@stream-io/feeds-client 0.2.16 → 0.2.18
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/CHANGELOG.md +21 -0
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/react-bindings.js +1 -1
- package/dist/es/index.mjs +2 -2
- package/dist/es/react-bindings.mjs +1 -1
- package/dist/{index-CaFrpjpl.js → index--koeDtxd.js} +310 -80
- package/dist/index--koeDtxd.js.map +1 -0
- package/dist/{index-J3MkoYPN.mjs → index-Zde8UE5f.mjs} +310 -80
- package/dist/index-Zde8UE5f.mjs.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/common/real-time/StableWSConnection.d.ts +3 -3
- package/dist/types/feed/event-handlers/activity/handle-activity-added.d.ts +4 -3
- package/dist/types/feed/event-handlers/activity/handle-activity-added.d.ts.map +1 -1
- package/dist/types/feed/event-handlers/activity/handle-activity-reaction-updated.d.ts +14 -0
- package/dist/types/feed/event-handlers/activity/handle-activity-reaction-updated.d.ts.map +1 -0
- package/dist/types/feed/event-handlers/activity/handle-activity-updated.d.ts.map +1 -1
- package/dist/types/feed/event-handlers/activity/index.d.ts +1 -0
- package/dist/types/feed/event-handlers/activity/index.d.ts.map +1 -1
- package/dist/types/feed/event-handlers/activity-updater.d.ts +44 -0
- package/dist/types/feed/event-handlers/activity-updater.d.ts.map +1 -0
- package/dist/types/feed/event-handlers/add-aggregated-activities-to-state.d.ts +6 -0
- package/dist/types/feed/event-handlers/add-aggregated-activities-to-state.d.ts.map +1 -0
- package/dist/types/feed/event-handlers/comment/handle-comment-reaction-added.d.ts.map +1 -1
- package/dist/types/feed/event-handlers/comment/handle-comment-reaction-updated.d.ts +6 -0
- package/dist/types/feed/event-handlers/comment/handle-comment-reaction-updated.d.ts.map +1 -0
- package/dist/types/feed/event-handlers/comment/index.d.ts +1 -0
- package/dist/types/feed/event-handlers/comment/index.d.ts.map +1 -1
- package/dist/types/feed/event-handlers/index.d.ts +3 -1
- package/dist/types/feed/event-handlers/index.d.ts.map +1 -1
- package/dist/types/feed/event-handlers/{aggregated-feed/handle-aggregated-feed-updated.d.ts → notification-feed/handle-notification-feed-updated.d.ts} +2 -11
- package/dist/types/feed/event-handlers/notification-feed/handle-notification-feed-updated.d.ts.map +1 -0
- package/dist/types/feed/event-handlers/notification-feed/index.d.ts +2 -0
- package/dist/types/feed/event-handlers/notification-feed/index.d.ts.map +1 -0
- package/dist/types/feed/event-handlers/story-feeds/handle-story-feeds-updated.d.ts +15 -0
- package/dist/types/feed/event-handlers/story-feeds/handle-story-feeds-updated.d.ts.map +1 -0
- package/dist/types/feed/event-handlers/story-feeds/index.d.ts +2 -0
- package/dist/types/feed/event-handlers/story-feeds/index.d.ts.map +1 -0
- package/dist/types/feed/feed.d.ts +9 -3
- package/dist/types/feed/feed.d.ts.map +1 -1
- package/dist/types/feeds-client/feeds-client.d.ts +5 -3
- package/dist/types/feeds-client/feeds-client.d.ts.map +1 -1
- package/dist/types/gen/feeds/FeedsApi.d.ts.map +1 -1
- package/dist/types/gen/models/index.d.ts +43 -452
- package/dist/types/gen/models/index.d.ts.map +1 -1
- package/dist/types/utils/state-update-queue.d.ts +5 -1
- package/dist/types/utils/state-update-queue.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/feed/event-handlers/activity/handle-activity-added.test.ts +16 -5
- package/src/feed/event-handlers/activity/handle-activity-added.ts +9 -11
- package/src/feed/event-handlers/activity/handle-activity-reaction-updated.test.ts +282 -0
- package/src/feed/event-handlers/activity/handle-activity-reaction-updated.ts +140 -0
- package/src/feed/event-handlers/activity/handle-activity-updated.ts +8 -16
- package/src/feed/event-handlers/activity/index.ts +1 -0
- package/src/feed/event-handlers/activity-updater.ts +15 -0
- package/src/feed/event-handlers/add-aggregated-activities-to-state.test.ts +510 -0
- package/src/feed/event-handlers/add-aggregated-activities-to-state.ts +72 -0
- package/src/feed/event-handlers/comment/handle-comment-reaction-added.ts +1 -2
- package/src/feed/event-handlers/comment/handle-comment-reaction-updated.test.ts +350 -0
- package/src/feed/event-handlers/comment/handle-comment-reaction-updated.ts +72 -0
- package/src/feed/event-handlers/comment/index.ts +1 -1
- package/src/feed/event-handlers/index.ts +3 -1
- package/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.test.ts +182 -0
- package/src/feed/event-handlers/{aggregated-feed/handle-aggregated-feed-updated.ts → notification-feed/handle-notification-feed-updated.ts} +2 -94
- package/src/feed/event-handlers/notification-feed/index.ts +1 -0
- package/src/feed/event-handlers/story-feeds/handle-story-feeds-updated.test.ts +45 -0
- package/src/feed/event-handlers/story-feeds/handle-story-feeds-updated.ts +122 -0
- package/src/feed/event-handlers/story-feeds/index.ts +1 -0
- package/src/feed/feed.ts +16 -2
- package/src/feeds-client/feeds-client.ts +36 -6
- package/src/gen/feeds/FeedsApi.ts +5 -0
- package/src/gen/model-decoders/decoders.ts +10 -4
- package/src/gen/models/index.ts +76 -835
- package/src/test-utils/response-generators.ts +89 -1
- package/src/utils/state-update-queue.ts +14 -2
- package/dist/index-CaFrpjpl.js.map +0 -1
- package/dist/index-J3MkoYPN.mjs.map +0 -1
- package/dist/types/feed/event-handlers/aggregated-feed/handle-aggregated-feed-updated.d.ts.map +0 -1
- package/dist/types/feed/event-handlers/aggregated-feed/index.d.ts +0 -2
- package/dist/types/feed/event-handlers/aggregated-feed/index.d.ts.map +0 -1
- package/src/feed/event-handlers/activity/activity-utils.test.ts +0 -252
- package/src/feed/event-handlers/aggregated-feed/handle-aggregated-feed-updated.test.ts +0 -644
- package/src/feed/event-handlers/aggregated-feed/index.ts +0 -1
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { Feed, handleCommentReactionUpdated } from '../../../feed';
|
|
3
|
+
import { FeedsClient } from '../../../feeds-client';
|
|
4
|
+
import {
|
|
5
|
+
generateCommentResponse,
|
|
6
|
+
generateFeedResponse,
|
|
7
|
+
generateOwnUser,
|
|
8
|
+
getHumanId,
|
|
9
|
+
generateFeedReactionResponse,
|
|
10
|
+
generateCommentReactionUpdatedEvent,
|
|
11
|
+
} from '../../../test-utils';
|
|
12
|
+
import type {
|
|
13
|
+
CommentResponse,
|
|
14
|
+
FeedsReactionResponse,
|
|
15
|
+
} from '../../../gen/models';
|
|
16
|
+
import { shouldUpdateState } from '../../../utils';
|
|
17
|
+
import type { EventPayload } from '../../../types-internal';
|
|
18
|
+
|
|
19
|
+
describe(handleCommentReactionUpdated.name, () => {
|
|
20
|
+
let feed: Feed;
|
|
21
|
+
let client: FeedsClient;
|
|
22
|
+
let currentUserId: string;
|
|
23
|
+
let activityId: string;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
client = new FeedsClient('mock-api-key');
|
|
27
|
+
currentUserId = getHumanId();
|
|
28
|
+
client.state.partialNext({
|
|
29
|
+
connected_user: generateOwnUser({ id: currentUserId }),
|
|
30
|
+
});
|
|
31
|
+
const feedResponse = generateFeedResponse({
|
|
32
|
+
id: 'main',
|
|
33
|
+
group_id: 'user',
|
|
34
|
+
created_by: { id: currentUserId },
|
|
35
|
+
});
|
|
36
|
+
feed = new Feed(
|
|
37
|
+
client,
|
|
38
|
+
feedResponse.group_id,
|
|
39
|
+
feedResponse.id,
|
|
40
|
+
feedResponse,
|
|
41
|
+
);
|
|
42
|
+
activityId = `activity-${getHumanId()}`;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('updates own_reactions for current user and updates comment fields from the event', () => {
|
|
46
|
+
const existingCommentId = `comment-${getHumanId()}`;
|
|
47
|
+
const existingReaction = generateFeedReactionResponse({
|
|
48
|
+
type: 'like',
|
|
49
|
+
user: { id: currentUserId },
|
|
50
|
+
activity_id: activityId,
|
|
51
|
+
comment_id: existingCommentId,
|
|
52
|
+
});
|
|
53
|
+
const existingComment = generateCommentResponse({
|
|
54
|
+
id: existingCommentId,
|
|
55
|
+
object_id: activityId,
|
|
56
|
+
latest_reactions: [],
|
|
57
|
+
reaction_groups: {},
|
|
58
|
+
own_reactions: [existingReaction],
|
|
59
|
+
});
|
|
60
|
+
feed.state.partialNext({
|
|
61
|
+
comments_by_entity_id: {
|
|
62
|
+
[activityId]: {
|
|
63
|
+
comments: [existingComment],
|
|
64
|
+
pagination: { sort: 'first' },
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const event = generateCommentReactionUpdatedEvent({
|
|
70
|
+
comment: {
|
|
71
|
+
id: existingComment.id,
|
|
72
|
+
object_id: activityId,
|
|
73
|
+
latest_reactions: [],
|
|
74
|
+
reaction_groups: {},
|
|
75
|
+
},
|
|
76
|
+
reaction: {
|
|
77
|
+
type: 'downvote',
|
|
78
|
+
user: { id: currentUserId },
|
|
79
|
+
activity_id: activityId,
|
|
80
|
+
comment_id: existingCommentId,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const stateBefore = feed.currentState;
|
|
85
|
+
const [initialReaction] = (stateBefore.comments_by_entity_id[activityId]
|
|
86
|
+
?.comments ?? [])[0].own_reactions;
|
|
87
|
+
expect(
|
|
88
|
+
stateBefore.comments_by_entity_id[activityId]?.comments?.[0]
|
|
89
|
+
?.own_reactions,
|
|
90
|
+
).toHaveLength(1);
|
|
91
|
+
expect(initialReaction).toBe(existingReaction);
|
|
92
|
+
|
|
93
|
+
handleCommentReactionUpdated.call(feed, event);
|
|
94
|
+
|
|
95
|
+
const stateAfter = feed.currentState;
|
|
96
|
+
const [updated] = stateAfter.comments_by_entity_id[activityId]!.comments!;
|
|
97
|
+
expect(updated.own_reactions).toHaveLength(1);
|
|
98
|
+
expect(updated.own_reactions[0]).toBe(event.reaction);
|
|
99
|
+
// ensure we used event's latest_reactions & reaction_groups (Object.is check)
|
|
100
|
+
expect(updated.latest_reactions).toBe(event.comment.latest_reactions);
|
|
101
|
+
expect(updated.reaction_groups).toBe(event.comment.reaction_groups);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('does modify own_reactions if the target reaction belongs to another user', () => {
|
|
105
|
+
const existingComment = generateCommentResponse({
|
|
106
|
+
object_id: activityId,
|
|
107
|
+
latest_reactions: [],
|
|
108
|
+
reaction_groups: {},
|
|
109
|
+
own_reactions: [],
|
|
110
|
+
});
|
|
111
|
+
feed.state.partialNext({
|
|
112
|
+
comments_by_entity_id: {
|
|
113
|
+
[activityId]: {
|
|
114
|
+
comments: [existingComment],
|
|
115
|
+
pagination: { sort: 'first' },
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const event = generateCommentReactionUpdatedEvent({
|
|
121
|
+
comment: {
|
|
122
|
+
id: existingComment.id,
|
|
123
|
+
object_id: activityId,
|
|
124
|
+
latest_reactions: [],
|
|
125
|
+
reaction_groups: {},
|
|
126
|
+
},
|
|
127
|
+
reaction: {
|
|
128
|
+
type: 'laugh',
|
|
129
|
+
user: { id: 'other-user' },
|
|
130
|
+
activity_id: activityId,
|
|
131
|
+
comment_id: existingComment.id,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
handleCommentReactionUpdated.call(feed, event);
|
|
136
|
+
const stateAfter = feed.currentState;
|
|
137
|
+
const [updated] = stateAfter.comments_by_entity_id[activityId]!.comments!;
|
|
138
|
+
expect(updated.own_reactions).toHaveLength(0);
|
|
139
|
+
expect(updated.latest_reactions).toBe(event.comment.latest_reactions);
|
|
140
|
+
expect(updated.reaction_groups).toBe(event.comment.reaction_groups);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('updates the proper entity state (prefers parent_id)', () => {
|
|
144
|
+
const parentId = `comment-${getHumanId()}`;
|
|
145
|
+
const existingCommentId = `comment-${getHumanId()}`;
|
|
146
|
+
const existingReaction = generateFeedReactionResponse({
|
|
147
|
+
type: 'like',
|
|
148
|
+
user: { id: currentUserId },
|
|
149
|
+
activity_id: activityId,
|
|
150
|
+
comment_id: existingCommentId,
|
|
151
|
+
});
|
|
152
|
+
const existingComment = generateCommentResponse({
|
|
153
|
+
id: existingCommentId,
|
|
154
|
+
object_id: activityId,
|
|
155
|
+
parent_id: parentId,
|
|
156
|
+
latest_reactions: [],
|
|
157
|
+
reaction_groups: {},
|
|
158
|
+
own_reactions: [existingReaction],
|
|
159
|
+
});
|
|
160
|
+
feed.state.partialNext({
|
|
161
|
+
comments_by_entity_id: {
|
|
162
|
+
[parentId]: {
|
|
163
|
+
comments: [existingComment],
|
|
164
|
+
pagination: { sort: 'first' },
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const addedEvent = generateCommentReactionUpdatedEvent({
|
|
170
|
+
comment: {
|
|
171
|
+
id: existingComment.id,
|
|
172
|
+
object_id: activityId,
|
|
173
|
+
parent_id: parentId,
|
|
174
|
+
latest_reactions: [],
|
|
175
|
+
reaction_groups: {},
|
|
176
|
+
},
|
|
177
|
+
reaction: {
|
|
178
|
+
type: 'downvote',
|
|
179
|
+
user: { id: currentUserId },
|
|
180
|
+
activity_id: activityId,
|
|
181
|
+
comment_id: existingComment.id,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const stateBefore = feed.currentState;
|
|
186
|
+
expect(
|
|
187
|
+
stateBefore.comments_by_entity_id[parentId]?.comments?.[0]?.own_reactions,
|
|
188
|
+
).toHaveLength(1);
|
|
189
|
+
const [initialReaction] = (stateBefore.comments_by_entity_id[parentId]
|
|
190
|
+
?.comments ?? [])[0].own_reactions;
|
|
191
|
+
expect(initialReaction).toBe(existingReaction);
|
|
192
|
+
|
|
193
|
+
handleCommentReactionUpdated.call(feed, addedEvent);
|
|
194
|
+
const stateAfter1 = feed.currentState;
|
|
195
|
+
const [updated1] = stateAfter1.comments_by_entity_id[parentId]!.comments!;
|
|
196
|
+
expect(updated1.own_reactions).toHaveLength(1);
|
|
197
|
+
expect(updated1.own_reactions[0]).toBe(addedEvent.reaction);
|
|
198
|
+
expect(updated1.latest_reactions).toBe(addedEvent.comment.latest_reactions);
|
|
199
|
+
expect(updated1.reaction_groups).toBe(addedEvent.comment.reaction_groups);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('does nothing if comment is not found in state', () => {
|
|
203
|
+
const addedEvent = generateCommentReactionUpdatedEvent({
|
|
204
|
+
comment: { object_id: activityId },
|
|
205
|
+
reaction: { user: { id: currentUserId } },
|
|
206
|
+
});
|
|
207
|
+
const stateBefore = feed.currentState;
|
|
208
|
+
|
|
209
|
+
handleCommentReactionUpdated.call(feed, addedEvent);
|
|
210
|
+
const stateAfter = feed.currentState;
|
|
211
|
+
expect(stateAfter).toBe(stateBefore);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe(`Comment reaction updated ${shouldUpdateState.name} integration`, () => {
|
|
215
|
+
let currentUserPayload: EventPayload<'feeds.comment.reaction.updated'>;
|
|
216
|
+
let existingComment: CommentResponse;
|
|
217
|
+
let existingReaction: FeedsReactionResponse;
|
|
218
|
+
let newReaction: FeedsReactionResponse;
|
|
219
|
+
let commentId: string;
|
|
220
|
+
|
|
221
|
+
beforeEach(() => {
|
|
222
|
+
commentId = `comment-${getHumanId()}`;
|
|
223
|
+
existingReaction = generateFeedReactionResponse({
|
|
224
|
+
type: 'heart',
|
|
225
|
+
user: { id: currentUserId },
|
|
226
|
+
activity_id: activityId,
|
|
227
|
+
comment_id: commentId,
|
|
228
|
+
});
|
|
229
|
+
newReaction = generateFeedReactionResponse({
|
|
230
|
+
type: 'like',
|
|
231
|
+
user: { id: currentUserId },
|
|
232
|
+
activity_id: activityId,
|
|
233
|
+
comment_id: commentId,
|
|
234
|
+
});
|
|
235
|
+
existingComment = generateCommentResponse({
|
|
236
|
+
id: commentId,
|
|
237
|
+
object_id: activityId,
|
|
238
|
+
own_reactions: [existingReaction],
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
currentUserPayload = generateCommentReactionUpdatedEvent({
|
|
242
|
+
comment: existingComment,
|
|
243
|
+
reaction: newReaction,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
feed.state.partialNext({
|
|
247
|
+
comments_by_entity_id: {
|
|
248
|
+
[activityId]: {
|
|
249
|
+
comments: [existingComment],
|
|
250
|
+
pagination: { sort: 'first' },
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
feed.state.partialNext({ watch: true });
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it(`skips update if ${shouldUpdateState.name} returns false`, () => {
|
|
258
|
+
// 1. HTTP and then WS
|
|
259
|
+
|
|
260
|
+
handleCommentReactionUpdated.call(feed, currentUserPayload, false);
|
|
261
|
+
|
|
262
|
+
let stateBefore = feed.currentState;
|
|
263
|
+
|
|
264
|
+
handleCommentReactionUpdated.call(feed, currentUserPayload);
|
|
265
|
+
|
|
266
|
+
let stateAfter = feed.currentState;
|
|
267
|
+
|
|
268
|
+
expect(stateAfter).toBe(stateBefore);
|
|
269
|
+
// @ts-expect-error Using Feed internals for tests only
|
|
270
|
+
expect(feed.stateUpdateQueue.size).toEqual(0);
|
|
271
|
+
|
|
272
|
+
// 2. WS and the HTTP
|
|
273
|
+
|
|
274
|
+
handleCommentReactionUpdated.call(feed, currentUserPayload);
|
|
275
|
+
|
|
276
|
+
stateBefore = feed.currentState;
|
|
277
|
+
|
|
278
|
+
handleCommentReactionUpdated.call(feed, currentUserPayload, false);
|
|
279
|
+
|
|
280
|
+
stateAfter = feed.currentState;
|
|
281
|
+
|
|
282
|
+
expect(stateAfter).toBe(stateBefore);
|
|
283
|
+
// @ts-expect-error Using Feed internals for tests only
|
|
284
|
+
expect(feed.stateUpdateQueue.size).toEqual(0);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('allows update again from WS after clearing the stateUpdateQueue', () => {
|
|
288
|
+
handleCommentReactionUpdated.call(feed, currentUserPayload);
|
|
289
|
+
|
|
290
|
+
// Clear the queue
|
|
291
|
+
(feed as any).stateUpdateQueue.clear();
|
|
292
|
+
|
|
293
|
+
// Now update should be allowed from another WS event
|
|
294
|
+
handleCommentReactionUpdated.call(feed, currentUserPayload);
|
|
295
|
+
|
|
296
|
+
const comments =
|
|
297
|
+
feed.currentState.comments_by_entity_id[activityId]?.comments;
|
|
298
|
+
const comment = comments?.find((a) => a.id === commentId);
|
|
299
|
+
const [latestReaction] = (comment?.own_reactions ?? []).toReversed();
|
|
300
|
+
|
|
301
|
+
expect(comment?.own_reactions.length).toEqual(1);
|
|
302
|
+
expect(latestReaction).toMatchObject(newReaction);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('allows update again from HTTP response after clearing the stateUpdateQueue', () => {
|
|
306
|
+
handleCommentReactionUpdated.call(feed, currentUserPayload, false);
|
|
307
|
+
|
|
308
|
+
// Clear the queue
|
|
309
|
+
(feed as any).stateUpdateQueue.clear();
|
|
310
|
+
|
|
311
|
+
// Now update should be allowed from another HTTP response
|
|
312
|
+
handleCommentReactionUpdated.call(feed, currentUserPayload, false);
|
|
313
|
+
|
|
314
|
+
const comments =
|
|
315
|
+
feed.currentState.comments_by_entity_id[activityId]?.comments;
|
|
316
|
+
const comment = comments?.find((a) => a.id === commentId);
|
|
317
|
+
const [latestReaction] = (comment?.own_reactions ?? []).toReversed();
|
|
318
|
+
|
|
319
|
+
expect(comment?.own_reactions.length).toEqual(1);
|
|
320
|
+
expect(latestReaction).toMatchObject(newReaction);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should not insert anything into the stateUpdateQueue if the connected_user did not trigger the comment reaction deletion', () => {
|
|
324
|
+
const otherUserPayload = generateCommentReactionUpdatedEvent({
|
|
325
|
+
comment: existingComment,
|
|
326
|
+
reaction: {
|
|
327
|
+
...existingReaction,
|
|
328
|
+
user: {
|
|
329
|
+
id: getHumanId(),
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
handleCommentReactionUpdated.call(feed, otherUserPayload);
|
|
335
|
+
|
|
336
|
+
expect((feed as any).stateUpdateQueue).toEqual(new Set());
|
|
337
|
+
|
|
338
|
+
handleCommentReactionUpdated.call(feed, otherUserPayload);
|
|
339
|
+
|
|
340
|
+
const comments =
|
|
341
|
+
feed.currentState.comments_by_entity_id[activityId]?.comments;
|
|
342
|
+
const comment = comments?.find((a) => a.id === commentId);
|
|
343
|
+
const [latestReaction] = comment?.own_reactions ?? [];
|
|
344
|
+
|
|
345
|
+
expect((feed as any).stateUpdateQueue).toEqual(new Set());
|
|
346
|
+
expect(comment?.own_reactions.length).toEqual(1);
|
|
347
|
+
expect(latestReaction).toMatchObject(existingReaction);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Feed } from '../../feed';
|
|
2
|
+
import type { EventPayload } from '../../../types-internal';
|
|
3
|
+
import { type PartializeAllBut } from '../../../types-internal';
|
|
4
|
+
import { getStateUpdateQueueId, shouldUpdateState } from '../../../utils';
|
|
5
|
+
|
|
6
|
+
export type CommentReactionUpdatedPayload = PartializeAllBut<
|
|
7
|
+
EventPayload<'feeds.comment.reaction.updated'>,
|
|
8
|
+
'comment' | 'reaction'
|
|
9
|
+
>;
|
|
10
|
+
|
|
11
|
+
export function handleCommentReactionUpdated(
|
|
12
|
+
this: Feed,
|
|
13
|
+
payload: CommentReactionUpdatedPayload,
|
|
14
|
+
fromWs?: boolean,
|
|
15
|
+
) {
|
|
16
|
+
const { comment, reaction } = payload;
|
|
17
|
+
const connectedUser = this.client.state.getLatestValue().connected_user;
|
|
18
|
+
|
|
19
|
+
const isOwnReaction = reaction.user.id === connectedUser?.id;
|
|
20
|
+
|
|
21
|
+
if (
|
|
22
|
+
!shouldUpdateState({
|
|
23
|
+
stateUpdateQueueId: getStateUpdateQueueId(
|
|
24
|
+
payload,
|
|
25
|
+
'comment-reaction-updated',
|
|
26
|
+
),
|
|
27
|
+
stateUpdateQueue: this.stateUpdateQueue,
|
|
28
|
+
watch: this.currentState.watch,
|
|
29
|
+
fromWs,
|
|
30
|
+
isTriggeredByConnectedUser: isOwnReaction,
|
|
31
|
+
})
|
|
32
|
+
) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.state.next((currentState) => {
|
|
37
|
+
const commentIndex = this.getCommentIndex(comment, currentState);
|
|
38
|
+
|
|
39
|
+
if (commentIndex === -1) return currentState;
|
|
40
|
+
|
|
41
|
+
const forId = comment.parent_id ?? comment.object_id;
|
|
42
|
+
|
|
43
|
+
const entityState = currentState.comments_by_entity_id[forId];
|
|
44
|
+
|
|
45
|
+
const newComments = entityState?.comments?.concat([]) ?? [];
|
|
46
|
+
|
|
47
|
+
let ownReactions = newComments[commentIndex].own_reactions;
|
|
48
|
+
|
|
49
|
+
if (isOwnReaction) {
|
|
50
|
+
ownReactions = [reaction];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
newComments[commentIndex] = {
|
|
54
|
+
...newComments[commentIndex],
|
|
55
|
+
reaction_count: comment.reaction_count ?? 0,
|
|
56
|
+
latest_reactions: comment.latest_reactions ?? [],
|
|
57
|
+
reaction_groups: comment.reaction_groups ?? {},
|
|
58
|
+
own_reactions: ownReactions,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
...currentState,
|
|
63
|
+
comments_by_entity_id: {
|
|
64
|
+
...currentState.comments_by_entity_id,
|
|
65
|
+
[forId]: {
|
|
66
|
+
...entityState,
|
|
67
|
+
comments: newComments,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -3,4 +3,4 @@ export * from './handle-comment-deleted';
|
|
|
3
3
|
export * from './handle-comment-updated';
|
|
4
4
|
export * from './handle-comment-reaction-added';
|
|
5
5
|
export * from './handle-comment-reaction-deleted';
|
|
6
|
-
|
|
6
|
+
export * from './handle-comment-reaction-updated';
|
|
@@ -4,5 +4,7 @@ export * from './feed-member';
|
|
|
4
4
|
export * from './bookmark';
|
|
5
5
|
export * from './activity';
|
|
6
6
|
export * from './feed';
|
|
7
|
-
export * from './
|
|
7
|
+
export * from './notification-feed';
|
|
8
|
+
export * from './story-feeds';
|
|
8
9
|
export * from './watch';
|
|
10
|
+
export * from './add-aggregated-activities-to-state';
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
updateNotificationFeedFromEvent,
|
|
4
|
+
updateNotificationStatus,
|
|
5
|
+
} from './handle-notification-feed-updated';
|
|
6
|
+
import {
|
|
7
|
+
createMockAggregatedActivity,
|
|
8
|
+
createMockNotificationFeedUpdatedEvent,
|
|
9
|
+
createMockNotificationStatus,
|
|
10
|
+
} from '../../../test-utils';
|
|
11
|
+
|
|
12
|
+
describe('notification-feed-utils', () => {
|
|
13
|
+
describe('updateNotificationFeedFromEvent', () => {
|
|
14
|
+
it('should return unchanged if event has no notification_status or aggregated_activities', () => {
|
|
15
|
+
const event = createMockNotificationFeedUpdatedEvent();
|
|
16
|
+
|
|
17
|
+
const result = updateNotificationFeedFromEvent(event, [], {
|
|
18
|
+
unread: 0,
|
|
19
|
+
unseen: 0,
|
|
20
|
+
read_activities: [],
|
|
21
|
+
seen_activities: [],
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
expect(result.changed).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it(`should update notification_status when event has notification_status and currentNotificationStatus is undefined`, () => {
|
|
28
|
+
const event = createMockNotificationFeedUpdatedEvent({
|
|
29
|
+
notification_status: createMockNotificationStatus(),
|
|
30
|
+
});
|
|
31
|
+
const result = updateNotificationFeedFromEvent(event, [], undefined);
|
|
32
|
+
|
|
33
|
+
expect(result.changed).toBe(true);
|
|
34
|
+
expect(result.data?.notification_status).toStrictEqual(
|
|
35
|
+
event.notification_status,
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it(`shouldn't update aggregated_activities when event has aggregated_activities but currentAggregatedActivities is undefined`, () => {
|
|
40
|
+
const event = createMockNotificationFeedUpdatedEvent({
|
|
41
|
+
aggregated_activities: [createMockAggregatedActivity()],
|
|
42
|
+
});
|
|
43
|
+
const result = updateNotificationFeedFromEvent(
|
|
44
|
+
event,
|
|
45
|
+
undefined,
|
|
46
|
+
createMockNotificationStatus(),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
expect(result.changed).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should update notification_status when event has notification_status', () => {
|
|
53
|
+
const notificationStatus = createMockNotificationStatus({
|
|
54
|
+
unread: 5,
|
|
55
|
+
unseen: 3,
|
|
56
|
+
read_activities: ['activity1', 'activity2'],
|
|
57
|
+
seen_activities: [],
|
|
58
|
+
});
|
|
59
|
+
const event = createMockNotificationFeedUpdatedEvent({
|
|
60
|
+
notification_status: notificationStatus,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const result = updateNotificationFeedFromEvent(event, [], {
|
|
64
|
+
unread: 0,
|
|
65
|
+
unseen: 0,
|
|
66
|
+
read_activities: [],
|
|
67
|
+
seen_activities: [],
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(result.changed).toBe(true);
|
|
71
|
+
expect(result.data?.notification_status).toStrictEqual(
|
|
72
|
+
notificationStatus,
|
|
73
|
+
);
|
|
74
|
+
expect(result.data?.aggregated_activities).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should update aggregated_activities when event has aggregated_activities', () => {
|
|
78
|
+
const aggregatedActivities = [
|
|
79
|
+
createMockAggregatedActivity({ group: 'group1' }),
|
|
80
|
+
createMockAggregatedActivity({ group: 'group2' }),
|
|
81
|
+
];
|
|
82
|
+
const event = createMockNotificationFeedUpdatedEvent({
|
|
83
|
+
aggregated_activities: aggregatedActivities,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const result = updateNotificationFeedFromEvent(event, [], {
|
|
87
|
+
unread: 0,
|
|
88
|
+
unseen: 0,
|
|
89
|
+
read_activities: [],
|
|
90
|
+
seen_activities: [],
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect(result.changed).toBe(true);
|
|
94
|
+
expect(result.data?.aggregated_activities).toStrictEqual(
|
|
95
|
+
aggregatedActivities,
|
|
96
|
+
);
|
|
97
|
+
expect(result.data?.notification_status).toBeUndefined();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should update both notification_status and aggregated_activities when event has both', () => {
|
|
101
|
+
const notificationStatus = createMockNotificationStatus({
|
|
102
|
+
unread: 2,
|
|
103
|
+
unseen: 1,
|
|
104
|
+
});
|
|
105
|
+
const aggregatedActivities = [
|
|
106
|
+
createMockAggregatedActivity({ group: 'group1' }),
|
|
107
|
+
];
|
|
108
|
+
const event = createMockNotificationFeedUpdatedEvent({
|
|
109
|
+
notification_status: notificationStatus,
|
|
110
|
+
aggregated_activities: aggregatedActivities,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const result = updateNotificationFeedFromEvent(event, [], {
|
|
114
|
+
unread: 0,
|
|
115
|
+
unseen: 0,
|
|
116
|
+
read_activities: [],
|
|
117
|
+
seen_activities: [],
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(result.changed).toBe(true);
|
|
121
|
+
expect(result.data?.notification_status?.unread).toBe(
|
|
122
|
+
notificationStatus.unread,
|
|
123
|
+
);
|
|
124
|
+
expect(result.data?.notification_status?.unseen).toBe(
|
|
125
|
+
notificationStatus.unseen,
|
|
126
|
+
);
|
|
127
|
+
expect(result.data?.aggregated_activities).toStrictEqual(
|
|
128
|
+
aggregatedActivities,
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should handle notification_status with all fields', () => {
|
|
133
|
+
const notificationStatus = createMockNotificationStatus({
|
|
134
|
+
unread: 10,
|
|
135
|
+
unseen: 5,
|
|
136
|
+
last_seen_at: new Date('2023-01-01'),
|
|
137
|
+
seen_activities: [],
|
|
138
|
+
read_activities: ['activity1', 'activity2', 'activity3'],
|
|
139
|
+
});
|
|
140
|
+
const event = createMockNotificationFeedUpdatedEvent({
|
|
141
|
+
notification_status: notificationStatus,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const result = updateNotificationFeedFromEvent(event, [], {
|
|
145
|
+
unread: 0,
|
|
146
|
+
unseen: 0,
|
|
147
|
+
read_activities: [],
|
|
148
|
+
seen_activities: [],
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
expect(result.changed).toBe(true);
|
|
152
|
+
expect(result.data?.notification_status).toStrictEqual(
|
|
153
|
+
notificationStatus,
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('updateNotificationStatus', () => {
|
|
159
|
+
it('should replace old state with new one', () => {
|
|
160
|
+
const newNotificationStatus = createMockNotificationStatus({
|
|
161
|
+
unread: 5,
|
|
162
|
+
unseen: 3,
|
|
163
|
+
read_activities: ['activity1', 'activity2'],
|
|
164
|
+
seen_activities: ['activity3', 'activity4'],
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const currentNotificationStatus = createMockNotificationStatus({
|
|
168
|
+
unread: 2,
|
|
169
|
+
unseen: 1,
|
|
170
|
+
read_activities: ['activity5', 'activity6'],
|
|
171
|
+
seen_activities: ['activity7', 'activity8'],
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const result = updateNotificationStatus(
|
|
175
|
+
newNotificationStatus,
|
|
176
|
+
currentNotificationStatus,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
expect(result.notification_status).toStrictEqual(newNotificationStatus);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|