@stream-io/feeds-client 0.2.18 → 0.2.20
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 +23 -0
- package/dist/cjs/index.js +94 -25
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react-bindings.js +26 -55
- package/dist/cjs/react-bindings.js.map +1 -1
- package/dist/es/index.mjs +86 -17
- package/dist/es/index.mjs.map +1 -1
- package/dist/es/react-bindings.mjs +19 -48
- package/dist/es/react-bindings.mjs.map +1 -1
- package/dist/{index-Zde8UE5f.mjs → feeds-client-BObWT4vl.mjs} +191 -92
- package/dist/feeds-client-BObWT4vl.mjs.map +1 -0
- package/dist/{index--koeDtxd.js → feeds-client-BlR_3zy2.js} +178 -79
- package/dist/feeds-client-BlR_3zy2.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/bindings/react/hooks/feed-state-hooks/useOwnCapabilities.d.ts +2 -32
- package/dist/types/bindings/react/hooks/feed-state-hooks/useOwnCapabilities.d.ts.map +1 -1
- package/dist/types/common/real-time/event-models.d.ts +7 -2
- package/dist/types/common/real-time/event-models.d.ts.map +1 -1
- package/dist/types/common/types.d.ts +1 -0
- package/dist/types/common/types.d.ts.map +1 -1
- package/dist/types/feed/event-handlers/activity/handle-activity-added.d.ts.map +1 -1
- package/dist/types/feed/event-handlers/activity/handle-activity-updated.d.ts.map +1 -1
- package/dist/types/feed/feed.d.ts +1 -1
- package/dist/types/feed/feed.d.ts.map +1 -1
- package/dist/types/feeds-client/feeds-client.d.ts +9 -1
- package/dist/types/feeds-client/feeds-client.d.ts.map +1 -1
- package/dist/types/utils/throttling/index.d.ts +3 -0
- package/dist/types/utils/throttling/index.d.ts.map +1 -0
- package/dist/types/utils/throttling/throttle.d.ts +34 -0
- package/dist/types/utils/throttling/throttle.d.ts.map +1 -0
- package/dist/types/utils/throttling/throttled-get-batched-own-capabilities.d.ts +14 -0
- package/dist/types/utils/throttling/throttled-get-batched-own-capabilities.d.ts.map +1 -0
- package/package.json +7 -3
- package/react-bindings.d.ts +11 -0
- package/react-bindings.js +7 -0
- package/react-bindings.mjs +11 -0
- package/src/bindings/react/hooks/feed-state-hooks/useOwnCapabilities.ts +21 -73
- package/src/common/real-time/event-models.ts +8 -2
- package/src/common/types.ts +1 -0
- package/src/feed/event-handlers/activity/handle-activity-added.ts +9 -1
- package/src/feed/event-handlers/activity/handle-activity-updated.ts +4 -0
- package/src/feed/feed.ts +18 -3
- package/src/feeds-client/feeds-client.ts +106 -3
- package/src/utils/throttling/index.ts +2 -0
- package/src/utils/throttling/throttle.ts +123 -0
- package/src/utils/throttling/throttled-get-batched-own-capabilities.ts +44 -0
- package/dist/index--koeDtxd.js.map +0 -1
- package/dist/index-Zde8UE5f.mjs.map +0 -1
- package/src/feed/event-handlers/activity/activity-marked-utils.test.ts +0 -208
- package/src/feed/event-handlers/activity/activity-reaction-utils.test.ts +0 -371
- package/src/feed/event-handlers/activity/handle-activity-added.test.ts +0 -97
- package/src/feed/event-handlers/activity/handle-activity-deleted.test.ts +0 -117
- package/src/feed/event-handlers/activity/handle-activity-pinned.test.ts +0 -60
- package/src/feed/event-handlers/activity/handle-activity-reaction-added.test.ts +0 -257
- package/src/feed/event-handlers/activity/handle-activity-reaction-deleted.test.ts +0 -317
- package/src/feed/event-handlers/activity/handle-activity-reaction-updated.test.ts +0 -282
- package/src/feed/event-handlers/activity/handle-activity-unpinned.test.ts +0 -95
- package/src/feed/event-handlers/activity/handle-activity-updated.test.ts +0 -245
- package/src/feed/event-handlers/add-aggregated-activities-to-state.test.ts +0 -510
- package/src/feed/event-handlers/bookmark/bookmark-utils.test.ts +0 -521
- package/src/feed/event-handlers/bookmark/handle-bookmark-added.test.ts +0 -178
- package/src/feed/event-handlers/bookmark/handle-bookmark-deleted.test.ts +0 -188
- package/src/feed/event-handlers/bookmark/handle-bookmark-updated.test.ts +0 -196
- package/src/feed/event-handlers/comment/handle-comment-added.test.ts +0 -271
- package/src/feed/event-handlers/comment/handle-comment-deleted.test.ts +0 -255
- package/src/feed/event-handlers/comment/handle-comment-reaction-added.test.ts +0 -329
- package/src/feed/event-handlers/comment/handle-comment-reaction-deleted.test.ts +0 -343
- package/src/feed/event-handlers/comment/handle-comment-reaction-updated.test.ts +0 -350
- package/src/feed/event-handlers/comment/handle-comment-updated.test.ts +0 -267
- package/src/feed/event-handlers/comment/utils/update-comment-count.test.ts +0 -322
- package/src/feed/event-handlers/feed-member/handle-feed-member-added.test.ts +0 -75
- package/src/feed/event-handlers/feed-member/handle-feed-member-removed.test.ts +0 -82
- package/src/feed/event-handlers/feed-member/handle-feed-member-updated.test.ts +0 -84
- package/src/feed/event-handlers/follow/follow-state-update-queue.test.ts +0 -219
- package/src/feed/event-handlers/follow/handle-follow-created.test.ts +0 -250
- package/src/feed/event-handlers/follow/handle-follow-deleted.test.ts +0 -268
- package/src/feed/event-handlers/follow/handle-follow-updated.test.ts +0 -131
- package/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.test.ts +0 -182
- package/src/feed/event-handlers/story-feeds/handle-story-feeds-updated.test.ts +0 -45
- package/src/feed/feed.test.ts +0 -90
- package/src/feeds-client/event-handlers/user/handle-user-updated.test.ts +0 -53
- package/src/utils/event-triggered-by-connected-user.test.ts +0 -73
- package/src/utils/state-update-queue.test.ts +0 -129
- package/src/utils/unique-array-merge.test.ts +0 -179
|
@@ -1,267 +0,0 @@
|
|
|
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
|
-
generateUserResponseCommonFields,
|
|
11
|
-
getHumanId,
|
|
12
|
-
} from '../../../test-utils';
|
|
13
|
-
import type { CommentResponse, UserResponseCommonFields } from '../../../gen/models';
|
|
14
|
-
import { shouldUpdateState } from '../../../utils';
|
|
15
|
-
import type { EventPayload } from '../../../types-internal';
|
|
16
|
-
|
|
17
|
-
describe(handleCommentUpdated.name, () => {
|
|
18
|
-
let feed: Feed;
|
|
19
|
-
let client: FeedsClient;
|
|
20
|
-
let currentUserId: string;
|
|
21
|
-
let activityId: string;
|
|
22
|
-
|
|
23
|
-
beforeEach(() => {
|
|
24
|
-
client = new FeedsClient('mock-api-key');
|
|
25
|
-
currentUserId = getHumanId();
|
|
26
|
-
client.state.partialNext({
|
|
27
|
-
connected_user: generateOwnUser({ id: currentUserId }),
|
|
28
|
-
});
|
|
29
|
-
const feedResponse = generateFeedResponse({
|
|
30
|
-
id: 'main',
|
|
31
|
-
group_id: 'user',
|
|
32
|
-
created_by: { id: currentUserId },
|
|
33
|
-
});
|
|
34
|
-
feed = new Feed(
|
|
35
|
-
client,
|
|
36
|
-
feedResponse.group_id,
|
|
37
|
-
feedResponse.id,
|
|
38
|
-
feedResponse,
|
|
39
|
-
);
|
|
40
|
-
activityId = `activity-${getHumanId()}`;
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('replaces the existing comment (activity level)', () => {
|
|
44
|
-
const original = generateCommentResponse({ object_id: activityId });
|
|
45
|
-
feed.state.partialNext({
|
|
46
|
-
comments_by_entity_id: {
|
|
47
|
-
[activityId]: { comments: [original], pagination: { sort: 'first' } },
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
const event = generateCommentUpdatedEvent({
|
|
52
|
-
comment: {
|
|
53
|
-
id: original.id,
|
|
54
|
-
object_id: activityId,
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const stateBefore = feed.currentState;
|
|
59
|
-
handleCommentUpdated.call(feed, event);
|
|
60
|
-
const stateAfter = feed.currentState;
|
|
61
|
-
|
|
62
|
-
expect(stateAfter).not.toBe(stateBefore);
|
|
63
|
-
const commentsAfter =
|
|
64
|
-
stateAfter.comments_by_entity_id[activityId]?.comments;
|
|
65
|
-
const commentsBefore =
|
|
66
|
-
stateBefore.comments_by_entity_id[activityId]?.comments;
|
|
67
|
-
expect(commentsAfter).not.toBe(commentsBefore);
|
|
68
|
-
expect(commentsAfter).toHaveLength(1);
|
|
69
|
-
const [replaced] = commentsAfter!;
|
|
70
|
-
expect(replaced).toBe(event.comment);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('updates the comment in the correct parent entity (prefers parent_id)', () => {
|
|
74
|
-
const parentId = `comment-${getHumanId()}`;
|
|
75
|
-
const reply = generateCommentResponse({
|
|
76
|
-
object_id: activityId,
|
|
77
|
-
parent_id: parentId,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
feed.state.partialNext({
|
|
81
|
-
comments_by_entity_id: {
|
|
82
|
-
[parentId]: { comments: [reply], pagination: { sort: 'first' } },
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const event = generateCommentUpdatedEvent({
|
|
87
|
-
comment: {
|
|
88
|
-
id: reply.id,
|
|
89
|
-
object_id: activityId,
|
|
90
|
-
parent_id: parentId,
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const stateBefore = feed.currentState;
|
|
95
|
-
handleCommentUpdated.call(feed, event);
|
|
96
|
-
const stateAfter = feed.currentState;
|
|
97
|
-
expect(stateAfter).not.toBe(stateBefore);
|
|
98
|
-
const commentsBefore =
|
|
99
|
-
stateBefore.comments_by_entity_id[parentId]?.comments;
|
|
100
|
-
const commentsAfter = stateAfter.comments_by_entity_id[parentId]?.comments;
|
|
101
|
-
expect(commentsAfter).not.toBe(commentsBefore);
|
|
102
|
-
expect(commentsAfter).toHaveLength(1);
|
|
103
|
-
const [updatedReply] = commentsAfter!;
|
|
104
|
-
expect(updatedReply).toBe(event.comment);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('does nothing if entity state does not exist', () => {
|
|
108
|
-
const event = generateCommentUpdatedEvent({
|
|
109
|
-
comment: { object_id: activityId },
|
|
110
|
-
});
|
|
111
|
-
const stateBefore = feed.currentState;
|
|
112
|
-
handleCommentUpdated.call(feed, event);
|
|
113
|
-
const stateAfter = feed.currentState;
|
|
114
|
-
expect(stateAfter).toBe(stateBefore);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('does nothing if comment not found in existing entity state', () => {
|
|
118
|
-
// set up state with different comment
|
|
119
|
-
feed.state.partialNext({
|
|
120
|
-
comments_by_entity_id: {
|
|
121
|
-
[activityId]: {
|
|
122
|
-
comments: [generateCommentResponse({ object_id: activityId })],
|
|
123
|
-
pagination: { sort: 'first' },
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
const event = generateCommentUpdatedEvent({
|
|
128
|
-
comment: { object_id: activityId },
|
|
129
|
-
});
|
|
130
|
-
const stateBefore = feed.currentState;
|
|
131
|
-
handleCommentUpdated.call(feed, event);
|
|
132
|
-
const stateAfter = feed.currentState;
|
|
133
|
-
expect(stateAfter).toBe(stateBefore);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
describe(`Comment updated ${shouldUpdateState.name} integration`, () => {
|
|
137
|
-
let currentUserPayload: EventPayload<'feeds.comment.updated'>;
|
|
138
|
-
let existingComment: CommentResponse;
|
|
139
|
-
let commentBeforeUpdate: CommentResponse;
|
|
140
|
-
let commentAfterUpdate: CommentResponse;
|
|
141
|
-
let commentId: string;
|
|
142
|
-
|
|
143
|
-
beforeEach(() => {
|
|
144
|
-
commentId = `comment-${getHumanId()}`;
|
|
145
|
-
commentBeforeUpdate = generateCommentResponse({
|
|
146
|
-
id: commentId,
|
|
147
|
-
object_id: activityId,
|
|
148
|
-
});
|
|
149
|
-
commentAfterUpdate = {
|
|
150
|
-
...commentBeforeUpdate,
|
|
151
|
-
reply_count: commentBeforeUpdate.reply_count + 10,
|
|
152
|
-
};
|
|
153
|
-
existingComment = generateCommentResponse({
|
|
154
|
-
id: `comment-${getHumanId()}`,
|
|
155
|
-
object_id: activityId,
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
currentUserPayload = generateCommentUpdatedEvent({
|
|
159
|
-
comment: commentAfterUpdate,
|
|
160
|
-
user: client.state.getLatestValue()
|
|
161
|
-
.connected_user as UserResponseCommonFields,
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
feed.state.partialNext({
|
|
165
|
-
comments_by_entity_id: {
|
|
166
|
-
[activityId]: {
|
|
167
|
-
comments: [existingComment, commentBeforeUpdate],
|
|
168
|
-
pagination: { sort: 'first' },
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
});
|
|
172
|
-
feed.state.partialNext({ watch: true });
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it(`skips update if ${shouldUpdateState.name} returns false`, () => {
|
|
176
|
-
// 1. HTTP and then WS
|
|
177
|
-
|
|
178
|
-
handleCommentUpdated.call(feed, currentUserPayload, false);
|
|
179
|
-
|
|
180
|
-
let stateBefore = feed.currentState;
|
|
181
|
-
|
|
182
|
-
handleCommentUpdated.call(feed, currentUserPayload);
|
|
183
|
-
|
|
184
|
-
let stateAfter = feed.currentState;
|
|
185
|
-
|
|
186
|
-
expect(stateAfter).toBe(stateBefore);
|
|
187
|
-
// @ts-expect-error Using Feed internals for tests only
|
|
188
|
-
expect(feed.stateUpdateQueue.size).toEqual(0);
|
|
189
|
-
|
|
190
|
-
// 2. WS and the HTTP
|
|
191
|
-
|
|
192
|
-
handleCommentUpdated.call(feed, currentUserPayload);
|
|
193
|
-
|
|
194
|
-
stateBefore = feed.currentState;
|
|
195
|
-
|
|
196
|
-
handleCommentUpdated.call(feed, currentUserPayload, false);
|
|
197
|
-
|
|
198
|
-
stateAfter = feed.currentState;
|
|
199
|
-
|
|
200
|
-
expect(stateAfter).toBe(stateBefore);
|
|
201
|
-
// @ts-expect-error Using Feed internals for tests only
|
|
202
|
-
expect(feed.stateUpdateQueue.size).toEqual(0);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it('allows update again from WS after clearing the stateUpdateQueue', () => {
|
|
206
|
-
handleCommentUpdated.call(feed, currentUserPayload);
|
|
207
|
-
|
|
208
|
-
// Clear the queue
|
|
209
|
-
(feed as any).stateUpdateQueue.clear();
|
|
210
|
-
|
|
211
|
-
// Now update should be allowed from another WS event
|
|
212
|
-
handleCommentUpdated.call(feed, currentUserPayload);
|
|
213
|
-
|
|
214
|
-
const comments =
|
|
215
|
-
feed.currentState.comments_by_entity_id[activityId]?.comments;
|
|
216
|
-
const [latestComment, previousComment] = (comments ?? []).toReversed();
|
|
217
|
-
|
|
218
|
-
expect(comments?.length).toEqual(2);
|
|
219
|
-
expect(latestComment).toMatchObject(commentAfterUpdate);
|
|
220
|
-
expect(previousComment).toMatchObject(existingComment);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('allows update again from HTTP response after clearing the stateUpdateQueue', () => {
|
|
224
|
-
handleCommentUpdated.call(feed, currentUserPayload, false);
|
|
225
|
-
|
|
226
|
-
// Clear the queue
|
|
227
|
-
(feed as any).stateUpdateQueue.clear();
|
|
228
|
-
|
|
229
|
-
// Now update should be allowed from another HTTP response
|
|
230
|
-
handleCommentUpdated.call(feed, currentUserPayload, false);
|
|
231
|
-
|
|
232
|
-
const comments =
|
|
233
|
-
feed.currentState.comments_by_entity_id[activityId]?.comments;
|
|
234
|
-
const [latestComment, previousComment] = (comments ?? []).toReversed();
|
|
235
|
-
|
|
236
|
-
expect(comments?.length).toEqual(2);
|
|
237
|
-
expect(latestComment).toMatchObject(commentAfterUpdate);
|
|
238
|
-
expect(previousComment).toMatchObject(existingComment);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it('should not insert anything into the stateUpdateQueue if the connected_user did not trigger the comment reaction deletion', () => {
|
|
242
|
-
const otherCommentUpdate = generateCommentResponse({
|
|
243
|
-
...commentBeforeUpdate,
|
|
244
|
-
reply_count: commentBeforeUpdate.reply_count + 1,
|
|
245
|
-
});
|
|
246
|
-
const otherUserPayload = generateCommentUpdatedEvent({
|
|
247
|
-
comment: otherCommentUpdate,
|
|
248
|
-
user: generateUserResponseCommonFields({ id: getHumanId() }),
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
handleCommentUpdated.call(feed, otherUserPayload);
|
|
252
|
-
|
|
253
|
-
expect((feed as any).stateUpdateQueue).toEqual(new Set());
|
|
254
|
-
|
|
255
|
-
handleCommentUpdated.call(feed, otherUserPayload);
|
|
256
|
-
|
|
257
|
-
const comments =
|
|
258
|
-
feed.currentState.comments_by_entity_id[activityId]?.comments;
|
|
259
|
-
const [latestComment, previousComment] = (comments ?? []).toReversed();
|
|
260
|
-
|
|
261
|
-
expect((feed as any).stateUpdateQueue).toEqual(new Set());
|
|
262
|
-
expect(comments?.length).toEqual(2);
|
|
263
|
-
expect(latestComment).toMatchObject(otherCommentUpdate);
|
|
264
|
-
expect(previousComment).toMatchObject(existingComment);
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
});
|
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { updateCommentCount as updateCommentCountInternal } from './update-comment-count';
|
|
4
|
-
import * as commentHandlers from '../handle-comment-updated';
|
|
5
|
-
import * as activityHandlers from '../../activity';
|
|
6
|
-
import { FeedsClient } from '../../../../feeds-client';
|
|
7
|
-
import { Feed } from '../../../feed';
|
|
8
|
-
import type { ActivityResponse, CommentResponse } from '../../../../gen/models';
|
|
9
|
-
import {
|
|
10
|
-
generateCommentResponse,
|
|
11
|
-
generateFeedResponse,
|
|
12
|
-
generateOwnUser,
|
|
13
|
-
getHumanId,
|
|
14
|
-
} from '../../../../test-utils';
|
|
15
|
-
|
|
16
|
-
vi.mock('../../activity', async (importOriginal) => {
|
|
17
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
18
|
-
const actual = await importOriginal<typeof import('../../activity')>();
|
|
19
|
-
return {
|
|
20
|
-
...actual,
|
|
21
|
-
handleActivityUpdated: vi.fn(),
|
|
22
|
-
};
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
vi.mock('../handle-comment-updated', async (importOriginal) => {
|
|
26
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
27
|
-
const actual = await importOriginal<typeof import('../handle-comment-updated')>();
|
|
28
|
-
return {
|
|
29
|
-
...actual,
|
|
30
|
-
handleCommentUpdated: vi.fn(),
|
|
31
|
-
};
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const handleCommentUpdated = commentHandlers.handleCommentUpdated as unknown as ReturnType<typeof vi.fn>;
|
|
35
|
-
const handleActivityUpdated = activityHandlers.handleActivityUpdated as unknown as ReturnType<typeof vi.fn>;
|
|
36
|
-
|
|
37
|
-
describe('updateCommentCount', () => {
|
|
38
|
-
let feed: Feed;
|
|
39
|
-
let client: FeedsClient;
|
|
40
|
-
let currentUserId: string;
|
|
41
|
-
let activityId: string;
|
|
42
|
-
let existingActivity: ActivityResponse;
|
|
43
|
-
|
|
44
|
-
let updateCommentCount: OmitThisParameter<typeof updateCommentCountInternal>;
|
|
45
|
-
|
|
46
|
-
beforeEach(() => {
|
|
47
|
-
client = new FeedsClient('mock-api-key');
|
|
48
|
-
currentUserId = getHumanId();
|
|
49
|
-
|
|
50
|
-
client.state.partialNext({
|
|
51
|
-
connected_user: generateOwnUser({ id: currentUserId }),
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const feedResponse = generateFeedResponse({
|
|
55
|
-
id: 'main',
|
|
56
|
-
group_id: 'user',
|
|
57
|
-
created_by: { id: currentUserId },
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
feed = new Feed(
|
|
61
|
-
client,
|
|
62
|
-
feedResponse.group_id,
|
|
63
|
-
feedResponse.id,
|
|
64
|
-
feedResponse,
|
|
65
|
-
);
|
|
66
|
-
feed.state.partialNext({ watch: false });
|
|
67
|
-
|
|
68
|
-
updateCommentCount = updateCommentCountInternal.bind(feed);
|
|
69
|
-
|
|
70
|
-
activityId = `activity-${getHumanId()}`;
|
|
71
|
-
existingActivity = { id: activityId, comment_count: 0 } as ActivityResponse;
|
|
72
|
-
|
|
73
|
-
feed.state.partialNext({ activities: [existingActivity] });
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
afterEach(() => {
|
|
77
|
-
vi.resetAllMocks();
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('correctly updates parent comment reply_count and comment_count (with watch: false)', () => {
|
|
81
|
-
const parentCommentId = 'c1';
|
|
82
|
-
|
|
83
|
-
const existingComment = generateCommentResponse({
|
|
84
|
-
id: parentCommentId,
|
|
85
|
-
object_id: activityId,
|
|
86
|
-
reply_count: 0,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
feed.state.partialNext({
|
|
90
|
-
activities: [{ ...existingActivity, comment_count: 1 }],
|
|
91
|
-
comments_by_entity_id: {
|
|
92
|
-
[activityId]: {
|
|
93
|
-
comments: [existingComment],
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const incomingReply = generateCommentResponse({
|
|
99
|
-
id: 'c2',
|
|
100
|
-
object_id: activityId,
|
|
101
|
-
parent_id: parentCommentId,
|
|
102
|
-
reply_count: 0,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
updateCommentCount({
|
|
106
|
-
activity: { ...existingActivity, comment_count: 11 },
|
|
107
|
-
comment: incomingReply,
|
|
108
|
-
replyCountUpdater: (prev: number) => prev + 1,
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
expect(handleCommentUpdated).toHaveBeenCalledTimes(1);
|
|
112
|
-
const [commentArg, secondArg] = handleCommentUpdated.mock.calls[0];
|
|
113
|
-
expect(secondArg).toBe(false);
|
|
114
|
-
expect(commentArg).toEqual({
|
|
115
|
-
comment: {
|
|
116
|
-
...existingComment,
|
|
117
|
-
reply_count: 1,
|
|
118
|
-
},
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
expect(handleActivityUpdated).toHaveBeenCalledTimes(1);
|
|
122
|
-
expect(handleActivityUpdated).toHaveBeenCalledWith({
|
|
123
|
-
activity: { id: activityId, comment_count: 11 },
|
|
124
|
-
}, false);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('updates parent comment reply_count when grandparent is missing (falls back to activity store)', () => {
|
|
128
|
-
const parentCommentId = 'c1';
|
|
129
|
-
|
|
130
|
-
const existingComment = generateCommentResponse({
|
|
131
|
-
id: parentCommentId,
|
|
132
|
-
object_id: activityId,
|
|
133
|
-
reply_count: 5,
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
feed.state.partialNext({
|
|
137
|
-
activities: [{ ...existingActivity, comment_count: 0 }],
|
|
138
|
-
comments_by_entity_id: {
|
|
139
|
-
[activityId]: {
|
|
140
|
-
comments: [existingComment],
|
|
141
|
-
pagination: { sort: 'first' },
|
|
142
|
-
},
|
|
143
|
-
},
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
const incomingReply = generateCommentResponse({
|
|
147
|
-
id: 'c2',
|
|
148
|
-
object_id: activityId,
|
|
149
|
-
parent_id: parentCommentId,
|
|
150
|
-
reply_count: 0,
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
updateCommentCount({
|
|
154
|
-
activity: { ...existingActivity, comment_count: 1 },
|
|
155
|
-
comment: incomingReply,
|
|
156
|
-
replyCountUpdater: (prev: number) => prev - 2,
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
expect(handleCommentUpdated).toHaveBeenCalledTimes(1);
|
|
160
|
-
expect(handleCommentUpdated).toHaveBeenCalledWith(
|
|
161
|
-
{ comment: { ...existingComment, reply_count: 3 } },
|
|
162
|
-
false,
|
|
163
|
-
);
|
|
164
|
-
expect(handleActivityUpdated).toHaveBeenCalledTimes(1);
|
|
165
|
-
expect(handleActivityUpdated).toHaveBeenCalledWith({
|
|
166
|
-
activity: { id: activityId, comment_count: 1 },
|
|
167
|
-
}, false);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('updates the correct parent when replying to a depth-3 comment (entity_parent_id points to the parent comment store)', () => {
|
|
171
|
-
const c1: CommentResponse = generateCommentResponse({ id: 'c1', object_id: activityId, reply_count: 0 });
|
|
172
|
-
const c2: CommentResponse = generateCommentResponse({ id: 'c2', object_id: activityId, parent_id: 'c1', reply_count: 7 });
|
|
173
|
-
|
|
174
|
-
feed.state.partialNext({
|
|
175
|
-
activities: [{ ...existingActivity, comment_count: 1 }],
|
|
176
|
-
comments_by_entity_id: {
|
|
177
|
-
[activityId]: {
|
|
178
|
-
comments: [c1],
|
|
179
|
-
},
|
|
180
|
-
['c1']: { entity_parent_id: activityId, comments: [c2] },
|
|
181
|
-
['c2']: { entity_parent_id: 'c1', comments: [] },
|
|
182
|
-
},
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
const incomingReplyToC2: CommentResponse = generateCommentResponse({
|
|
186
|
-
id: 'c3',
|
|
187
|
-
object_id: activityId,
|
|
188
|
-
parent_id: 'c2',
|
|
189
|
-
reply_count: 0,
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
updateCommentCount({
|
|
193
|
-
activity: { ...existingActivity, comment_count: 1 },
|
|
194
|
-
comment: incomingReplyToC2,
|
|
195
|
-
replyCountUpdater: (prev: number) => prev + 5,
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
expect(handleCommentUpdated).toHaveBeenCalledTimes(1);
|
|
199
|
-
expect(handleCommentUpdated).toHaveBeenCalledWith(
|
|
200
|
-
{ comment: { ...c2, reply_count: 12 } },
|
|
201
|
-
false,
|
|
202
|
-
);
|
|
203
|
-
expect(handleActivityUpdated).toHaveBeenCalledTimes(1);
|
|
204
|
-
expect(handleActivityUpdated).toHaveBeenCalledWith({
|
|
205
|
-
activity: { id: activityId, comment_count: 1 },
|
|
206
|
-
}, false);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
it('does nothing on parent reply when parent comment is not found in the resolved store', () => {
|
|
210
|
-
feed.state.partialNext({
|
|
211
|
-
activities: [{ ...existingActivity, comment_count: 4 }],
|
|
212
|
-
comments_by_entity_id: {
|
|
213
|
-
[activityId]: { comments: [], },
|
|
214
|
-
},
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
const incomingReply: CommentResponse = generateCommentResponse({
|
|
218
|
-
id: 'child',
|
|
219
|
-
object_id: activityId,
|
|
220
|
-
parent_id: 'missing-parent',
|
|
221
|
-
reply_count: 0,
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
updateCommentCount({
|
|
225
|
-
activity: { ...existingActivity, comment_count: 5 },
|
|
226
|
-
comment: incomingReply,
|
|
227
|
-
replyCountUpdater: (prev: number) => prev + 1,
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
expect(handleCommentUpdated).not.toHaveBeenCalled();
|
|
231
|
-
// the activity still updates, as watch === true
|
|
232
|
-
expect(handleActivityUpdated).toHaveBeenCalledTimes(1);
|
|
233
|
-
expect(handleActivityUpdated).toHaveBeenCalledWith({
|
|
234
|
-
activity: { id: activityId, comment_count: 5 },
|
|
235
|
-
}, false);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it('skips parent reply update when comment has no parent_id (top-level comment)', () => {
|
|
239
|
-
feed.state.partialNext({
|
|
240
|
-
activities: [{ ...existingActivity, comment_count: 1 }],
|
|
241
|
-
comments_by_entity_id: {
|
|
242
|
-
[activityId]: { comments: [], },
|
|
243
|
-
},
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
const topLevel: CommentResponse = generateCommentResponse({
|
|
247
|
-
id: 'cTop',
|
|
248
|
-
object_id: activityId,
|
|
249
|
-
reply_count: 0,
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
updateCommentCount({
|
|
253
|
-
activity: { ...existingActivity, comment_count: 3 },
|
|
254
|
-
comment: topLevel,
|
|
255
|
-
replyCountUpdater: (prev: number) => prev + 100,
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
expect(handleCommentUpdated).not.toHaveBeenCalled();
|
|
259
|
-
expect(handleActivityUpdated).toHaveBeenCalledTimes(1);
|
|
260
|
-
expect(handleActivityUpdated).toHaveBeenCalledWith({
|
|
261
|
-
activity: { id: activityId, comment_count: 3 },
|
|
262
|
-
}, false);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it('no-ops any update if neither activity nor comment are found', () => {
|
|
266
|
-
feed.state.partialNext({
|
|
267
|
-
activities: [{ id: 'other-1', comment_count: 1 } as ActivityResponse, { id: 'other-2', comment_count: 3 } as ActivityResponse],
|
|
268
|
-
comments_by_entity_id: {},
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
const topLevel: CommentResponse = generateCommentResponse({
|
|
272
|
-
id: 'cTop',
|
|
273
|
-
object_id: 'missing-activity',
|
|
274
|
-
reply_count: 0,
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
updateCommentCount({
|
|
278
|
-
activity: { ...existingActivity, comment_count: 3 },
|
|
279
|
-
comment: topLevel,
|
|
280
|
-
replyCountUpdater: (prev: number) => prev + 1,
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
expect(handleActivityUpdated).not.toHaveBeenCalled();
|
|
284
|
-
expect(handleCommentUpdated).not.toHaveBeenCalled();
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
it('can update both parent reply_count and activity comment_count in the same call', () => {
|
|
288
|
-
const parentCommentId = 'c1';
|
|
289
|
-
const c1: CommentResponse = generateCommentResponse({ id: parentCommentId, object_id: activityId, reply_count: 10 });
|
|
290
|
-
|
|
291
|
-
feed.state.partialNext({
|
|
292
|
-
activities: [{ ...existingActivity, comment_count: 100 }],
|
|
293
|
-
comments_by_entity_id: {
|
|
294
|
-
[activityId]: { comments: [c1] },
|
|
295
|
-
[parentCommentId]: { entity_parent_id: activityId },
|
|
296
|
-
},
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
const incomingReply: CommentResponse = generateCommentResponse({
|
|
300
|
-
id: 'c2',
|
|
301
|
-
object_id: activityId,
|
|
302
|
-
parent_id: parentCommentId,
|
|
303
|
-
reply_count: 0,
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
updateCommentCount({
|
|
307
|
-
activity: { ...existingActivity, comment_count: 97 },
|
|
308
|
-
comment: incomingReply,
|
|
309
|
-
replyCountUpdater: (prev: number) => prev + 2,
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
expect(handleCommentUpdated).toHaveBeenCalledTimes(1);
|
|
313
|
-
expect(handleCommentUpdated).toHaveBeenCalledWith(
|
|
314
|
-
{ comment: { ...c1, reply_count: 12 } },
|
|
315
|
-
false,
|
|
316
|
-
);
|
|
317
|
-
expect(handleActivityUpdated).toHaveBeenCalledTimes(1);
|
|
318
|
-
expect(handleActivityUpdated).toHaveBeenCalledWith({
|
|
319
|
-
activity: { id: activityId, comment_count: 97 },
|
|
320
|
-
}, false);
|
|
321
|
-
});
|
|
322
|
-
});
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
-
import { Feed } from '../../../feed';
|
|
3
|
-
import { FeedsClient } from '../../../feeds-client';
|
|
4
|
-
import { handleFeedMemberAdded } from './handle-feed-member-added';
|
|
5
|
-
import {
|
|
6
|
-
generateFeedResponse,
|
|
7
|
-
generateFeedMemberAddedEvent,
|
|
8
|
-
generateFeedMemberResponse,
|
|
9
|
-
generateOwnUser,
|
|
10
|
-
getHumanId,
|
|
11
|
-
} from '../../../test-utils/response-generators';
|
|
12
|
-
|
|
13
|
-
describe(handleFeedMemberAdded.name, () => {
|
|
14
|
-
let feed: Feed;
|
|
15
|
-
let client: FeedsClient;
|
|
16
|
-
let currentUserId: string;
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
client = new FeedsClient('mock-api-key');
|
|
20
|
-
currentUserId = getHumanId();
|
|
21
|
-
client.state.partialNext({
|
|
22
|
-
connected_user: generateOwnUser({ id: currentUserId }),
|
|
23
|
-
});
|
|
24
|
-
const feedResponse = generateFeedResponse({
|
|
25
|
-
id: 'main',
|
|
26
|
-
group_id: 'user',
|
|
27
|
-
created_by: { id: currentUserId },
|
|
28
|
-
});
|
|
29
|
-
feed = new Feed(
|
|
30
|
-
client,
|
|
31
|
-
feedResponse.group_id,
|
|
32
|
-
feedResponse.id,
|
|
33
|
-
feedResponse,
|
|
34
|
-
);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('prepends to members if members array exists', () => {
|
|
38
|
-
const existingMember = generateFeedMemberResponse();
|
|
39
|
-
feed.state.partialNext({ members: [existingMember] });
|
|
40
|
-
|
|
41
|
-
const event = generateFeedMemberAddedEvent();
|
|
42
|
-
|
|
43
|
-
handleFeedMemberAdded.call(feed, event);
|
|
44
|
-
|
|
45
|
-
const stateAfter = feed.currentState;
|
|
46
|
-
expect(stateAfter.members).toHaveLength(2);
|
|
47
|
-
expect(stateAfter.members?.[0]).toBe(event.member);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('does not create members array if it does not exist', () => {
|
|
51
|
-
const event = generateFeedMemberAddedEvent();
|
|
52
|
-
|
|
53
|
-
const stateBefore = feed.currentState;
|
|
54
|
-
expect(stateBefore.members).toBeUndefined();
|
|
55
|
-
|
|
56
|
-
handleFeedMemberAdded.call(feed, event);
|
|
57
|
-
|
|
58
|
-
const stateAfter = feed.currentState;
|
|
59
|
-
expect(stateAfter).toBe(stateBefore);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('sets own_membership when the added member is the connected user', () => {
|
|
63
|
-
const event = generateFeedMemberAddedEvent({
|
|
64
|
-
member: { user: { id: currentUserId } },
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
const stateBefore = feed.currentState;
|
|
68
|
-
expect(stateBefore.own_membership).toBeUndefined();
|
|
69
|
-
|
|
70
|
-
handleFeedMemberAdded.call(feed, event);
|
|
71
|
-
|
|
72
|
-
const stateAfter = feed.currentState;
|
|
73
|
-
expect(stateAfter.own_membership).toBe(event.member);
|
|
74
|
-
});
|
|
75
|
-
});
|