@stream-io/feeds-client 0.2.1 → 0.2.2
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/@react-bindings/hooks/feed-state-hooks/index.ts +4 -0
- package/CHANGELOG.md +8 -0
- package/dist/@react-bindings/hooks/feed-state-hooks/index.d.ts +4 -0
- package/dist/@react-bindings/hooks/feed-state-hooks/useAggregatedActivities.d.ts +11 -0
- package/dist/@react-bindings/hooks/feed-state-hooks/useIsAggregatedActivityRead.d.ts +6 -0
- package/dist/@react-bindings/hooks/feed-state-hooks/useIsAggregatedActivitySeen.d.ts +6 -0
- package/dist/@react-bindings/hooks/feed-state-hooks/useNotificationStatus.d.ts +13 -0
- package/dist/@react-bindings/wrappers/StreamFeed.d.ts +1 -1
- package/dist/index-react-bindings.browser.cjs +154 -29
- package/dist/index-react-bindings.browser.cjs.map +1 -1
- package/dist/index-react-bindings.browser.js +151 -30
- package/dist/index-react-bindings.browser.js.map +1 -1
- package/dist/index-react-bindings.node.cjs +154 -29
- package/dist/index-react-bindings.node.cjs.map +1 -1
- package/dist/index-react-bindings.node.js +151 -30
- package/dist/index-react-bindings.node.js.map +1 -1
- package/dist/index.browser.cjs +88 -12
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.js +88 -12
- package/dist/index.browser.js.map +1 -1
- package/dist/index.node.cjs +88 -12
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.js +88 -12
- package/dist/index.node.js.map +1 -1
- package/dist/src/feed/event-handlers/activity/handle-activity-marked.d.ts +11 -0
- package/dist/src/feed/event-handlers/activity/index.d.ts +1 -0
- package/dist/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.d.ts +8 -1
- package/dist/src/feed/feed.d.ts +2 -2
- package/dist/src/test-utils/response-generators.d.ts +21 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/feed/event-handlers/activity/activity-marked-utils.test.ts +208 -0
- package/src/feed/event-handlers/activity/handle-activity-marked.ts +68 -0
- package/src/feed/event-handlers/activity/handle-activity-reaction-added.test.ts +15 -15
- package/src/feed/event-handlers/activity/handle-activity-reaction-deleted.test.ts +14 -14
- package/src/feed/event-handlers/activity/handle-activity-unpinned.test.ts +4 -3
- package/src/feed/event-handlers/activity/handle-activity-updated.test.ts +4 -4
- package/src/feed/event-handlers/activity/index.ts +2 -1
- package/src/feed/event-handlers/bookmark/handle-bookmark-added.test.ts +14 -14
- package/src/feed/event-handlers/bookmark/handle-bookmark-deleted.test.ts +14 -14
- package/src/feed/event-handlers/bookmark/handle-bookmark-updated.test.ts +16 -16
- package/src/feed/event-handlers/comment/handle-comment-added.test.ts +147 -0
- package/src/feed/event-handlers/comment/handle-comment-deleted.test.ts +133 -0
- package/src/feed/event-handlers/comment/handle-comment-deleted.ts +24 -10
- package/src/feed/event-handlers/comment/handle-comment-reaction.test.ts +315 -0
- package/src/feed/event-handlers/comment/handle-comment-updated.test.ts +131 -0
- package/src/feed/event-handlers/follow/handle-follow-created.test.ts +7 -7
- package/src/feed/event-handlers/follow/handle-follow-deleted.test.ts +2 -2
- package/src/feed/event-handlers/follow/handle-follow-updated.test.ts +1 -1
- package/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.test.ts +120 -0
- package/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.ts +47 -3
- package/src/feed/feed.ts +4 -2
- package/src/gen/model-decoders/decoders.ts +1 -1
- package/src/test-utils/response-generators.ts +123 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { Feed } from '../../../feed';
|
|
3
|
+
import { FeedsClient } from '../../../feeds-client';
|
|
4
|
+
import { handleCommentReaction } from './handle-comment-reaction';
|
|
5
|
+
import {
|
|
6
|
+
generateCommentResponse,
|
|
7
|
+
generateFeedReactionResponse,
|
|
8
|
+
generateFeedResponse,
|
|
9
|
+
generateOwnUser,
|
|
10
|
+
getHumanId,
|
|
11
|
+
generateCommentReactionAddedEvent,
|
|
12
|
+
generateCommentReactionDeletedEvent,
|
|
13
|
+
} from '../../../test-utils/response-generators';
|
|
14
|
+
|
|
15
|
+
describe(handleCommentReaction.name, () => {
|
|
16
|
+
let feed: Feed;
|
|
17
|
+
let client: FeedsClient;
|
|
18
|
+
let currentUserId: string;
|
|
19
|
+
let activityId: string;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
client = new FeedsClient('mock-api-key');
|
|
23
|
+
currentUserId = getHumanId();
|
|
24
|
+
client.state.partialNext({
|
|
25
|
+
connected_user: generateOwnUser({ id: currentUserId }),
|
|
26
|
+
});
|
|
27
|
+
const feedResponse = generateFeedResponse({
|
|
28
|
+
id: 'main',
|
|
29
|
+
group_id: 'user',
|
|
30
|
+
created_by: { id: currentUserId },
|
|
31
|
+
});
|
|
32
|
+
feed = new Feed(
|
|
33
|
+
client,
|
|
34
|
+
feedResponse.group_id,
|
|
35
|
+
feedResponse.id,
|
|
36
|
+
feedResponse,
|
|
37
|
+
);
|
|
38
|
+
activityId = `activity-${getHumanId()}`;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('adds to own_reactions for current user and updates comment fields from the event', () => {
|
|
42
|
+
const existingComment = generateCommentResponse({
|
|
43
|
+
object_id: activityId,
|
|
44
|
+
latest_reactions: [],
|
|
45
|
+
reaction_groups: {},
|
|
46
|
+
own_reactions: [],
|
|
47
|
+
});
|
|
48
|
+
feed.state.partialNext({
|
|
49
|
+
comments_by_entity_id: {
|
|
50
|
+
[activityId]: {
|
|
51
|
+
comments: [existingComment],
|
|
52
|
+
pagination: { sort: 'first' },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const event = generateCommentReactionAddedEvent({
|
|
58
|
+
comment: {
|
|
59
|
+
id: existingComment.id,
|
|
60
|
+
object_id: activityId,
|
|
61
|
+
latest_reactions: [],
|
|
62
|
+
reaction_groups: {},
|
|
63
|
+
},
|
|
64
|
+
reaction: {
|
|
65
|
+
type: 'like',
|
|
66
|
+
user: { id: currentUserId },
|
|
67
|
+
activity_id: activityId,
|
|
68
|
+
comment_id: existingComment.id,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const stateBefore = feed.currentState;
|
|
73
|
+
expect(
|
|
74
|
+
stateBefore.comments_by_entity_id[activityId]?.comments?.[0]
|
|
75
|
+
?.own_reactions,
|
|
76
|
+
).toHaveLength(0);
|
|
77
|
+
|
|
78
|
+
handleCommentReaction.call(feed, event);
|
|
79
|
+
|
|
80
|
+
const stateAfter = feed.currentState;
|
|
81
|
+
const [updated] = stateAfter.comments_by_entity_id[activityId]!.comments!;
|
|
82
|
+
expect(updated.own_reactions).toHaveLength(1);
|
|
83
|
+
expect(updated.own_reactions[0]).toBe(event.reaction);
|
|
84
|
+
// ensure we used event's latest_reactions & reaction_groups (Object.is check)
|
|
85
|
+
expect(updated.latest_reactions).toBe(event.comment.latest_reactions);
|
|
86
|
+
expect(updated.reaction_groups).toBe(event.comment.reaction_groups);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('does not add to own_reactions if the target reaction belongs to another user', () => {
|
|
90
|
+
const existingComment = generateCommentResponse({
|
|
91
|
+
object_id: activityId,
|
|
92
|
+
latest_reactions: [],
|
|
93
|
+
reaction_groups: {},
|
|
94
|
+
own_reactions: [],
|
|
95
|
+
});
|
|
96
|
+
feed.state.partialNext({
|
|
97
|
+
comments_by_entity_id: {
|
|
98
|
+
[activityId]: {
|
|
99
|
+
comments: [existingComment],
|
|
100
|
+
pagination: { sort: 'first' },
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const event = generateCommentReactionAddedEvent({
|
|
106
|
+
comment: {
|
|
107
|
+
id: existingComment.id,
|
|
108
|
+
object_id: activityId,
|
|
109
|
+
latest_reactions: [],
|
|
110
|
+
reaction_groups: {},
|
|
111
|
+
},
|
|
112
|
+
reaction: {
|
|
113
|
+
type: 'laugh',
|
|
114
|
+
user: { id: 'other-user' },
|
|
115
|
+
activity_id: activityId,
|
|
116
|
+
comment_id: existingComment.id,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
handleCommentReaction.call(feed, event);
|
|
121
|
+
const stateAfter = feed.currentState;
|
|
122
|
+
const [updated] = stateAfter.comments_by_entity_id[activityId]!.comments!;
|
|
123
|
+
expect(updated.own_reactions).toHaveLength(0);
|
|
124
|
+
expect(updated.latest_reactions).toBe(event.comment.latest_reactions);
|
|
125
|
+
expect(updated.reaction_groups).toBe(event.comment.reaction_groups);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('removes from own_reactions when the target reaction belongs to the current user', () => {
|
|
129
|
+
const commentId = `comment-${getHumanId()}`;
|
|
130
|
+
const existingReaction = generateFeedReactionResponse({
|
|
131
|
+
type: 'heart',
|
|
132
|
+
user: { id: currentUserId },
|
|
133
|
+
activity_id: activityId,
|
|
134
|
+
comment_id: commentId,
|
|
135
|
+
});
|
|
136
|
+
const existingComment = generateCommentResponse({
|
|
137
|
+
id: commentId,
|
|
138
|
+
object_id: activityId,
|
|
139
|
+
own_reactions: [existingReaction],
|
|
140
|
+
latest_reactions: [],
|
|
141
|
+
reaction_groups: {},
|
|
142
|
+
});
|
|
143
|
+
feed.state.partialNext({
|
|
144
|
+
comments_by_entity_id: {
|
|
145
|
+
[activityId]: {
|
|
146
|
+
comments: [existingComment],
|
|
147
|
+
pagination: { sort: 'first' },
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const event = generateCommentReactionDeletedEvent({
|
|
153
|
+
comment: {
|
|
154
|
+
...existingComment,
|
|
155
|
+
latest_reactions: [],
|
|
156
|
+
reaction_groups: {},
|
|
157
|
+
},
|
|
158
|
+
reaction: {
|
|
159
|
+
type: 'heart',
|
|
160
|
+
user: { id: currentUserId },
|
|
161
|
+
activity_id: activityId,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const stateBefore = feed.currentState;
|
|
166
|
+
expect(
|
|
167
|
+
stateBefore.comments_by_entity_id[activityId]!.comments![0].own_reactions,
|
|
168
|
+
).toHaveLength(1);
|
|
169
|
+
handleCommentReaction.call(feed, event);
|
|
170
|
+
const stateAfter = feed.currentState;
|
|
171
|
+
const [updated] = stateAfter.comments_by_entity_id[activityId]!.comments!;
|
|
172
|
+
expect(updated.own_reactions).toHaveLength(0);
|
|
173
|
+
expect(updated.latest_reactions).toBe(event.comment.latest_reactions);
|
|
174
|
+
expect(updated.reaction_groups).toBe(event.comment.reaction_groups);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('does not remove from own_reactions when target reaction does not belong to the current user', () => {
|
|
178
|
+
const commentId = `comment-${getHumanId()}`;
|
|
179
|
+
const ownReaction = generateFeedReactionResponse({
|
|
180
|
+
type: 'wow',
|
|
181
|
+
user: { id: currentUserId },
|
|
182
|
+
activity_id: activityId,
|
|
183
|
+
comment_id: commentId,
|
|
184
|
+
});
|
|
185
|
+
const existingComment = generateCommentResponse({
|
|
186
|
+
id: commentId,
|
|
187
|
+
object_id: activityId,
|
|
188
|
+
own_reactions: [ownReaction],
|
|
189
|
+
latest_reactions: [],
|
|
190
|
+
reaction_groups: {},
|
|
191
|
+
});
|
|
192
|
+
feed.state.partialNext({
|
|
193
|
+
comments_by_entity_id: {
|
|
194
|
+
[activityId]: {
|
|
195
|
+
comments: [existingComment],
|
|
196
|
+
pagination: { sort: 'first' },
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const event = generateCommentReactionDeletedEvent({
|
|
202
|
+
comment: {
|
|
203
|
+
...existingComment,
|
|
204
|
+
latest_reactions: [],
|
|
205
|
+
reaction_groups: {},
|
|
206
|
+
},
|
|
207
|
+
reaction: {
|
|
208
|
+
type: 'wow',
|
|
209
|
+
user: { id: 'other-user' },
|
|
210
|
+
comment_id: existingComment.id,
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
handleCommentReaction.call(feed, event);
|
|
215
|
+
const stateAfter = feed.currentState;
|
|
216
|
+
const [updated] = stateAfter.comments_by_entity_id[activityId]!.comments!;
|
|
217
|
+
expect(updated.own_reactions).toHaveLength(1);
|
|
218
|
+
expect(updated.own_reactions[0]).toBe(ownReaction);
|
|
219
|
+
expect(updated.latest_reactions).toBe(event.comment.latest_reactions);
|
|
220
|
+
expect(updated.reaction_groups).toBe(event.comment.reaction_groups);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('does changes to the proper entity state (prefers parent_id)', () => {
|
|
224
|
+
const parentId = `comment-${getHumanId()}`;
|
|
225
|
+
const existingComment = generateCommentResponse({
|
|
226
|
+
object_id: activityId,
|
|
227
|
+
parent_id: parentId,
|
|
228
|
+
latest_reactions: [],
|
|
229
|
+
reaction_groups: {},
|
|
230
|
+
own_reactions: [],
|
|
231
|
+
});
|
|
232
|
+
feed.state.partialNext({
|
|
233
|
+
comments_by_entity_id: {
|
|
234
|
+
[parentId]: {
|
|
235
|
+
comments: [existingComment],
|
|
236
|
+
pagination: { sort: 'first' },
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const addedEvent = generateCommentReactionAddedEvent({
|
|
242
|
+
comment: {
|
|
243
|
+
id: existingComment.id,
|
|
244
|
+
object_id: activityId,
|
|
245
|
+
parent_id: parentId,
|
|
246
|
+
latest_reactions: [],
|
|
247
|
+
reaction_groups: {},
|
|
248
|
+
},
|
|
249
|
+
reaction: {
|
|
250
|
+
type: 'like',
|
|
251
|
+
user: { id: currentUserId },
|
|
252
|
+
activity_id: activityId,
|
|
253
|
+
comment_id: existingComment.id,
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const deletedEvent = generateCommentReactionDeletedEvent({
|
|
258
|
+
comment: {
|
|
259
|
+
id: existingComment.id,
|
|
260
|
+
object_id: activityId,
|
|
261
|
+
parent_id: parentId,
|
|
262
|
+
latest_reactions: [],
|
|
263
|
+
reaction_groups: {},
|
|
264
|
+
},
|
|
265
|
+
reaction: {
|
|
266
|
+
type: 'like',
|
|
267
|
+
user: { id: currentUserId },
|
|
268
|
+
activity_id: activityId,
|
|
269
|
+
comment_id: existingComment.id,
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const stateBefore = feed.currentState;
|
|
274
|
+
expect(
|
|
275
|
+
stateBefore.comments_by_entity_id[parentId]?.comments?.[0]?.own_reactions,
|
|
276
|
+
).toHaveLength(0);
|
|
277
|
+
|
|
278
|
+
handleCommentReaction.call(feed, addedEvent);
|
|
279
|
+
const stateAfter1 = feed.currentState;
|
|
280
|
+
const [updated1] = stateAfter1.comments_by_entity_id[parentId]!.comments!;
|
|
281
|
+
expect(updated1.own_reactions).toHaveLength(1);
|
|
282
|
+
expect(updated1.own_reactions[0]).toBe(addedEvent.reaction);
|
|
283
|
+
expect(updated1.latest_reactions).toBe(addedEvent.comment.latest_reactions);
|
|
284
|
+
expect(updated1.reaction_groups).toBe(addedEvent.comment.reaction_groups);
|
|
285
|
+
|
|
286
|
+
handleCommentReaction.call(feed, deletedEvent);
|
|
287
|
+
const stateAfter2 = feed.currentState;
|
|
288
|
+
const [updated2] = stateAfter2.comments_by_entity_id[parentId]!.comments!;
|
|
289
|
+
expect(updated2.own_reactions).toHaveLength(0);
|
|
290
|
+
expect(updated2.latest_reactions).toBe(
|
|
291
|
+
deletedEvent.comment.latest_reactions,
|
|
292
|
+
);
|
|
293
|
+
expect(updated2.reaction_groups).toBe(deletedEvent.comment.reaction_groups);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('does nothing if comment is not found in state', () => {
|
|
297
|
+
const addedEvent = generateCommentReactionAddedEvent({
|
|
298
|
+
comment: { object_id: activityId },
|
|
299
|
+
reaction: { user: { id: currentUserId } },
|
|
300
|
+
});
|
|
301
|
+
const deletedEvent = generateCommentReactionDeletedEvent({
|
|
302
|
+
comment: { object_id: activityId },
|
|
303
|
+
reaction: { user: { id: currentUserId } },
|
|
304
|
+
});
|
|
305
|
+
const stateBefore = feed.currentState;
|
|
306
|
+
|
|
307
|
+
handleCommentReaction.call(feed, addedEvent);
|
|
308
|
+
const stateAfter1 = feed.currentState;
|
|
309
|
+
expect(stateAfter1).toBe(stateBefore);
|
|
310
|
+
|
|
311
|
+
handleCommentReaction.call(feed, deletedEvent);
|
|
312
|
+
const stateAfter2 = feed.currentState;
|
|
313
|
+
expect(stateAfter2).toBe(stateBefore);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { Feed } from '../../../feed';
|
|
3
|
+
import { FeedsClient } from '../../../feeds-client';
|
|
4
|
+
import { handleCommentUpdated } from './handle-comment-updated';
|
|
5
|
+
import {
|
|
6
|
+
generateCommentResponse,
|
|
7
|
+
generateCommentUpdatedEvent,
|
|
8
|
+
generateFeedResponse,
|
|
9
|
+
generateOwnUser,
|
|
10
|
+
getHumanId,
|
|
11
|
+
} from '../../../test-utils/response-generators';
|
|
12
|
+
|
|
13
|
+
describe(handleCommentUpdated.name, () => {
|
|
14
|
+
let feed: Feed;
|
|
15
|
+
let client: FeedsClient;
|
|
16
|
+
let currentUserId: string;
|
|
17
|
+
let activityId: string;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
client = new FeedsClient('mock-api-key');
|
|
21
|
+
currentUserId = getHumanId();
|
|
22
|
+
client.state.partialNext({
|
|
23
|
+
connected_user: generateOwnUser({ id: currentUserId }),
|
|
24
|
+
});
|
|
25
|
+
const feedResponse = generateFeedResponse({
|
|
26
|
+
id: 'main',
|
|
27
|
+
group_id: 'user',
|
|
28
|
+
created_by: { id: currentUserId },
|
|
29
|
+
});
|
|
30
|
+
feed = new Feed(
|
|
31
|
+
client,
|
|
32
|
+
feedResponse.group_id,
|
|
33
|
+
feedResponse.id,
|
|
34
|
+
feedResponse,
|
|
35
|
+
);
|
|
36
|
+
activityId = `activity-${getHumanId()}`;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('replaces the existing comment (activity level)', () => {
|
|
40
|
+
const original = generateCommentResponse({ object_id: activityId });
|
|
41
|
+
feed.state.partialNext({
|
|
42
|
+
comments_by_entity_id: {
|
|
43
|
+
[activityId]: { comments: [original], pagination: { sort: 'first' } },
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const event = generateCommentUpdatedEvent({
|
|
48
|
+
comment: {
|
|
49
|
+
id: original.id,
|
|
50
|
+
object_id: activityId,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const stateBefore = feed.currentState;
|
|
55
|
+
handleCommentUpdated.call(feed, event);
|
|
56
|
+
const stateAfter = feed.currentState;
|
|
57
|
+
|
|
58
|
+
expect(stateAfter).not.toBe(stateBefore);
|
|
59
|
+
const commentsAfter =
|
|
60
|
+
stateAfter.comments_by_entity_id[activityId]?.comments;
|
|
61
|
+
const commentsBefore =
|
|
62
|
+
stateBefore.comments_by_entity_id[activityId]?.comments;
|
|
63
|
+
expect(commentsAfter).not.toBe(commentsBefore);
|
|
64
|
+
expect(commentsAfter).toHaveLength(1);
|
|
65
|
+
const [replaced] = commentsAfter!;
|
|
66
|
+
expect(replaced).toBe(event.comment);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('updates the comment in the correct parent entity (prefers parent_id)', () => {
|
|
70
|
+
const parentId = `comment-${getHumanId()}`;
|
|
71
|
+
const reply = generateCommentResponse({
|
|
72
|
+
object_id: activityId,
|
|
73
|
+
parent_id: parentId,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
feed.state.partialNext({
|
|
77
|
+
comments_by_entity_id: {
|
|
78
|
+
[parentId]: { comments: [reply], pagination: { sort: 'first' } },
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const event = generateCommentUpdatedEvent({
|
|
83
|
+
comment: {
|
|
84
|
+
id: reply.id,
|
|
85
|
+
object_id: activityId,
|
|
86
|
+
parent_id: parentId,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const stateBefore = feed.currentState;
|
|
91
|
+
handleCommentUpdated.call(feed, event);
|
|
92
|
+
const stateAfter = feed.currentState;
|
|
93
|
+
expect(stateAfter).not.toBe(stateBefore);
|
|
94
|
+
const commentsBefore =
|
|
95
|
+
stateBefore.comments_by_entity_id[parentId]?.comments;
|
|
96
|
+
const commentsAfter = stateAfter.comments_by_entity_id[parentId]?.comments;
|
|
97
|
+
expect(commentsAfter).not.toBe(commentsBefore);
|
|
98
|
+
expect(commentsAfter).toHaveLength(1);
|
|
99
|
+
const [updatedReply] = commentsAfter!;
|
|
100
|
+
expect(updatedReply).toBe(event.comment);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('does nothing if entity state does not exist', () => {
|
|
104
|
+
const event = generateCommentUpdatedEvent({
|
|
105
|
+
comment: { object_id: activityId },
|
|
106
|
+
});
|
|
107
|
+
const stateBefore = feed.currentState;
|
|
108
|
+
handleCommentUpdated.call(feed, event);
|
|
109
|
+
const stateAfter = feed.currentState;
|
|
110
|
+
expect(stateAfter).toBe(stateBefore);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('does nothing if comment not found in existing entity state', () => {
|
|
114
|
+
// set up state with different comment
|
|
115
|
+
feed.state.partialNext({
|
|
116
|
+
comments_by_entity_id: {
|
|
117
|
+
[activityId]: {
|
|
118
|
+
comments: [generateCommentResponse({ object_id: activityId })],
|
|
119
|
+
pagination: { sort: 'first' },
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
const event = generateCommentUpdatedEvent({
|
|
124
|
+
comment: { object_id: activityId },
|
|
125
|
+
});
|
|
126
|
+
const stateBefore = feed.currentState;
|
|
127
|
+
handleCommentUpdated.call(feed, event);
|
|
128
|
+
const stateAfter = feed.currentState;
|
|
129
|
+
expect(stateAfter).toBe(stateBefore);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -78,10 +78,10 @@ describe('handle-follow-created', () => {
|
|
|
78
78
|
|
|
79
79
|
expect(result.changed).toBe(true);
|
|
80
80
|
expect(result.data.following).toHaveLength(1);
|
|
81
|
-
expect(result.data.following?.[0]).
|
|
81
|
+
expect(result.data.following?.[0]).toBe(follow);
|
|
82
82
|
expect(result.data).toMatchObject(follow.source_feed);
|
|
83
83
|
expect(result.data.own_follows).toBeUndefined();
|
|
84
|
-
expect(result.data.following_count).
|
|
84
|
+
expect(result.data.following_count).toBe(1);
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
it('should handle when someone follows this feed', () => {
|
|
@@ -120,10 +120,10 @@ describe('handle-follow-created', () => {
|
|
|
120
120
|
|
|
121
121
|
expect(result.changed).toBe(true);
|
|
122
122
|
expect(result.data.followers).toHaveLength(1);
|
|
123
|
-
expect(result.data.followers?.[0]).
|
|
123
|
+
expect(result.data.followers?.[0]).toBe(follow);
|
|
124
124
|
expect(result.data).toMatchObject(follow.target_feed);
|
|
125
125
|
expect(result.data.own_follows).toBeUndefined();
|
|
126
|
-
expect(result.data.follower_count).
|
|
126
|
+
expect(result.data.follower_count).toBe(1);
|
|
127
127
|
});
|
|
128
128
|
|
|
129
129
|
it('should add to own_follows when connected user is the source', () => {
|
|
@@ -161,7 +161,7 @@ describe('handle-follow-created', () => {
|
|
|
161
161
|
|
|
162
162
|
expect(result.changed).toBe(true);
|
|
163
163
|
expect(result.data.own_follows).toHaveLength(1);
|
|
164
|
-
expect(result.data.own_follows?.[0]).
|
|
164
|
+
expect(result.data.own_follows?.[0]).toBe(follow);
|
|
165
165
|
});
|
|
166
166
|
|
|
167
167
|
it('should not update followers/following when they are undefined', () => {
|
|
@@ -243,8 +243,8 @@ describe('handle-follow-created', () => {
|
|
|
243
243
|
|
|
244
244
|
expect(result.changed).toBe(true);
|
|
245
245
|
expect(result.data.followers).toHaveLength(2);
|
|
246
|
-
expect(result.data.followers?.[0]).
|
|
247
|
-
expect(result.data.followers?.[1]).
|
|
246
|
+
expect(result.data.followers?.[0]).toBe(follow);
|
|
247
|
+
expect(result.data.followers?.[1]).toBe(existingFollow);
|
|
248
248
|
});
|
|
249
249
|
});
|
|
250
250
|
});
|
|
@@ -95,7 +95,7 @@ describe('handle-follow-deleted', () => {
|
|
|
95
95
|
|
|
96
96
|
expect(result.changed).toBe(true);
|
|
97
97
|
expect(result.data.followers).toHaveLength(0);
|
|
98
|
-
expect(result.data.own_follows).
|
|
98
|
+
expect(result.data.own_follows).toBe(currentState.own_follows);
|
|
99
99
|
expect(result.data).toMatchObject(follow.target_feed);
|
|
100
100
|
});
|
|
101
101
|
|
|
@@ -262,7 +262,7 @@ describe('handle-follow-deleted', () => {
|
|
|
262
262
|
|
|
263
263
|
expect(result.changed).toBe(true);
|
|
264
264
|
expect(result.data.following).toHaveLength(1);
|
|
265
|
-
expect(result.data.following?.[0]).
|
|
265
|
+
expect(result.data.following?.[0]).toBe(followToKeep);
|
|
266
266
|
});
|
|
267
267
|
});
|
|
268
268
|
});
|
|
@@ -148,7 +148,7 @@ describe(handleFollowUpdated.name, () => {
|
|
|
148
148
|
|
|
149
149
|
// State should not change
|
|
150
150
|
const stateAfter = feed.currentState;
|
|
151
|
-
expect(stateAfter).
|
|
151
|
+
expect(stateAfter).toBe(stateBefore);
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
it('allows update again after clearing stateUpdateQueue', () => {
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
NotificationFeedUpdatedEvent,
|
|
4
|
+
NotificationStatusResponse,
|
|
5
|
+
AggregatedActivityResponse,
|
|
6
|
+
} from '../../../gen/models';
|
|
7
|
+
import { updateNotificationFeedFromEvent } from './handle-notification-feed-updated';
|
|
8
|
+
|
|
9
|
+
const createMockNotificationFeedUpdatedEvent = (
|
|
10
|
+
overrides: Partial<NotificationFeedUpdatedEvent> = {},
|
|
11
|
+
): NotificationFeedUpdatedEvent => ({
|
|
12
|
+
created_at: new Date(),
|
|
13
|
+
fid: 'user:notification',
|
|
14
|
+
custom: {},
|
|
15
|
+
type: 'feeds.notification_feed.updated',
|
|
16
|
+
...overrides,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const createMockNotificationStatus = (
|
|
20
|
+
overrides: Partial<NotificationStatusResponse> = {},
|
|
21
|
+
): NotificationStatusResponse => ({
|
|
22
|
+
unread: 0,
|
|
23
|
+
unseen: 0,
|
|
24
|
+
...overrides,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const createMockAggregatedActivity = (
|
|
28
|
+
overrides: Partial<AggregatedActivityResponse> = {},
|
|
29
|
+
): AggregatedActivityResponse => ({
|
|
30
|
+
activity_count: 1,
|
|
31
|
+
created_at: new Date(),
|
|
32
|
+
group: 'test-group',
|
|
33
|
+
score: 1,
|
|
34
|
+
updated_at: new Date(),
|
|
35
|
+
user_count: 1,
|
|
36
|
+
activities: [],
|
|
37
|
+
...overrides,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('notification-feed-utils', () => {
|
|
41
|
+
describe('updateNotificationFeedFromEvent', () => {
|
|
42
|
+
it('should return unchanged if event has no notification_status or aggregated_activities', () => {
|
|
43
|
+
const event = createMockNotificationFeedUpdatedEvent();
|
|
44
|
+
|
|
45
|
+
const result = updateNotificationFeedFromEvent(event);
|
|
46
|
+
|
|
47
|
+
expect(result.changed).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should update notification_status when event has notification_status', () => {
|
|
51
|
+
const notificationStatus = createMockNotificationStatus({
|
|
52
|
+
unread: 5,
|
|
53
|
+
unseen: 3,
|
|
54
|
+
read_activities: ['activity1', 'activity2'],
|
|
55
|
+
});
|
|
56
|
+
const event = createMockNotificationFeedUpdatedEvent({
|
|
57
|
+
notification_status: notificationStatus,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const result = updateNotificationFeedFromEvent(event);
|
|
61
|
+
|
|
62
|
+
expect(result.changed).toBe(true);
|
|
63
|
+
expect(result.data?.notification_status).toBe(notificationStatus);
|
|
64
|
+
expect(result.data?.aggregated_activities).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should update aggregated_activities when event has aggregated_activities', () => {
|
|
68
|
+
const aggregatedActivities = [
|
|
69
|
+
createMockAggregatedActivity({ group: 'group1' }),
|
|
70
|
+
createMockAggregatedActivity({ group: 'group2' }),
|
|
71
|
+
];
|
|
72
|
+
const event = createMockNotificationFeedUpdatedEvent({
|
|
73
|
+
aggregated_activities: aggregatedActivities,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const result = updateNotificationFeedFromEvent(event);
|
|
77
|
+
|
|
78
|
+
expect(result.changed).toBe(true);
|
|
79
|
+
expect(result.data?.aggregated_activities).toBe(aggregatedActivities);
|
|
80
|
+
expect(result.data?.notification_status).toBeUndefined();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should update both notification_status and aggregated_activities when event has both', () => {
|
|
84
|
+
const notificationStatus = createMockNotificationStatus({
|
|
85
|
+
unread: 2,
|
|
86
|
+
unseen: 1,
|
|
87
|
+
});
|
|
88
|
+
const aggregatedActivities = [
|
|
89
|
+
createMockAggregatedActivity({ group: 'group1' }),
|
|
90
|
+
];
|
|
91
|
+
const event = createMockNotificationFeedUpdatedEvent({
|
|
92
|
+
notification_status: notificationStatus,
|
|
93
|
+
aggregated_activities: aggregatedActivities,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const result = updateNotificationFeedFromEvent(event);
|
|
97
|
+
|
|
98
|
+
expect(result.changed).toBe(true);
|
|
99
|
+
expect(result.data?.notification_status).toBe(notificationStatus);
|
|
100
|
+
expect(result.data?.aggregated_activities).toBe(aggregatedActivities);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should handle notification_status with all fields', () => {
|
|
104
|
+
const notificationStatus = createMockNotificationStatus({
|
|
105
|
+
unread: 10,
|
|
106
|
+
unseen: 5,
|
|
107
|
+
last_seen_at: new Date('2023-01-01'),
|
|
108
|
+
read_activities: ['activity1', 'activity2', 'activity3'],
|
|
109
|
+
});
|
|
110
|
+
const event = createMockNotificationFeedUpdatedEvent({
|
|
111
|
+
notification_status: notificationStatus,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const result = updateNotificationFeedFromEvent(event);
|
|
115
|
+
|
|
116
|
+
expect(result.changed).toBe(true);
|
|
117
|
+
expect(result.data?.notification_status).toBe(notificationStatus);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -1,10 +1,54 @@
|
|
|
1
1
|
import type { Feed } from '../../../feed';
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
AggregatedActivityResponse,
|
|
4
|
+
NotificationFeedUpdatedEvent,
|
|
5
|
+
NotificationStatusResponse,
|
|
6
|
+
} from '../../../gen/models';
|
|
7
|
+
import type { EventPayload, UpdateStateResult } from '../../../types-internal';
|
|
8
|
+
|
|
9
|
+
export const updateNotificationFeedFromEvent = (
|
|
10
|
+
event: NotificationFeedUpdatedEvent,
|
|
11
|
+
): UpdateStateResult<{
|
|
12
|
+
data?: {
|
|
13
|
+
notification_status?: NotificationStatusResponse;
|
|
14
|
+
aggregated_activities?: AggregatedActivityResponse[];
|
|
15
|
+
};
|
|
16
|
+
}> => {
|
|
17
|
+
const updates: {
|
|
18
|
+
notification_status?: NotificationStatusResponse;
|
|
19
|
+
aggregated_activities?: AggregatedActivityResponse[];
|
|
20
|
+
} = {};
|
|
21
|
+
|
|
22
|
+
if (event.notification_status) {
|
|
23
|
+
updates.notification_status = event.notification_status;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (event.aggregated_activities) {
|
|
27
|
+
updates.aggregated_activities = event.aggregated_activities;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Only return changed if we have actual updates
|
|
31
|
+
if (Object.keys(updates).length > 0) {
|
|
32
|
+
return {
|
|
33
|
+
changed: true,
|
|
34
|
+
data: updates,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
changed: false,
|
|
40
|
+
};
|
|
41
|
+
};
|
|
3
42
|
|
|
4
43
|
export function handleNotificationFeedUpdated(
|
|
5
44
|
this: Feed,
|
|
6
45
|
event: EventPayload<'feeds.notification_feed.updated'>,
|
|
7
46
|
) {
|
|
8
|
-
|
|
9
|
-
|
|
47
|
+
const result = updateNotificationFeedFromEvent(event);
|
|
48
|
+
if (result.changed) {
|
|
49
|
+
this.state.partialNext({
|
|
50
|
+
notification_status: result.data?.notification_status,
|
|
51
|
+
aggregated_activities: result.data?.aggregated_activities,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
10
54
|
}
|