@stream-io/feeds-client 0.1.7 → 0.1.9
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/util/index.ts +1 -0
- package/CHANGELOG.md +20 -0
- package/dist/@react-bindings/hooks/util/index.d.ts +1 -0
- package/dist/@react-bindings/hooks/util/useBookmarkActions.d.ts +13 -0
- package/dist/@react-bindings/hooks/util/useReactionActions.d.ts +1 -1
- package/dist/index-react-bindings.browser.cjs +363 -141
- package/dist/index-react-bindings.browser.cjs.map +1 -1
- package/dist/index-react-bindings.browser.js +363 -142
- package/dist/index-react-bindings.browser.js.map +1 -1
- package/dist/index-react-bindings.node.cjs +363 -141
- package/dist/index-react-bindings.node.cjs.map +1 -1
- package/dist/index-react-bindings.node.js +363 -142
- package/dist/index-react-bindings.node.js.map +1 -1
- package/dist/index.browser.cjs +337 -140
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.js +337 -141
- package/dist/index.browser.js.map +1 -1
- package/dist/index.node.cjs +337 -140
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.js +337 -141
- package/dist/index.node.js.map +1 -1
- package/dist/src/Feed.d.ts +42 -11
- package/dist/src/FeedsClient.d.ts +10 -3
- package/dist/src/common/real-time/StableWSConnection.d.ts +3 -3
- package/dist/src/gen/models/index.d.ts +25 -2
- package/dist/src/gen-imports.d.ts +1 -1
- package/dist/src/state-updates/follow-utils.d.ts +19 -0
- package/dist/src/state-updates/state-update-queue.d.ts +15 -0
- package/dist/src/utils.d.ts +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/Feed.ts +230 -192
- package/src/FeedsClient.ts +75 -3
- package/src/gen/feeds/FeedsApi.ts +0 -1
- package/src/gen/model-decoders/decoders.ts +16 -0
- package/src/gen/model-decoders/event-decoder-mapping.ts +3 -0
- package/src/gen/models/index.ts +42 -4
- package/src/gen-imports.ts +1 -1
- package/src/state-updates/activity-reaction-utils.test.ts +1 -0
- package/src/state-updates/activity-utils.test.ts +1 -0
- package/src/state-updates/follow-utils.test.ts +552 -0
- package/src/state-updates/follow-utils.ts +126 -0
- package/src/state-updates/state-update-queue.test.ts +53 -0
- package/src/state-updates/state-update-queue.ts +35 -0
- package/src/utils.test.ts +175 -0
- package/src/utils.ts +20 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
handleFollowCreated,
|
|
5
|
+
handleFollowDeleted,
|
|
6
|
+
handleFollowUpdated,
|
|
7
|
+
} from './follow-utils';
|
|
8
|
+
import { FollowResponse, FeedResponse, UserResponse } from '../gen/models';
|
|
9
|
+
import { FeedState } from '../Feed';
|
|
10
|
+
|
|
11
|
+
describe('follow-utils', () => {
|
|
12
|
+
const mockUser: UserResponse = {
|
|
13
|
+
id: 'user-1',
|
|
14
|
+
created_at: new Date(),
|
|
15
|
+
updated_at: new Date(),
|
|
16
|
+
banned: false,
|
|
17
|
+
language: 'en',
|
|
18
|
+
online: false,
|
|
19
|
+
role: 'user',
|
|
20
|
+
blocked_user_ids: [],
|
|
21
|
+
teams: [],
|
|
22
|
+
custom: {},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const mockFeed: FeedResponse = {
|
|
26
|
+
id: 'feed-1',
|
|
27
|
+
group_id: 'user',
|
|
28
|
+
created_at: new Date(),
|
|
29
|
+
updated_at: new Date(),
|
|
30
|
+
description: 'Test feed',
|
|
31
|
+
fid: 'user:feed-1',
|
|
32
|
+
follower_count: 0,
|
|
33
|
+
following_count: 0,
|
|
34
|
+
member_count: 0,
|
|
35
|
+
name: 'Test Feed',
|
|
36
|
+
pin_count: 0,
|
|
37
|
+
created_by: mockUser,
|
|
38
|
+
custom: {},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const mockFollow: FollowResponse = {
|
|
42
|
+
created_at: new Date(),
|
|
43
|
+
updated_at: new Date(),
|
|
44
|
+
follower_role: 'user',
|
|
45
|
+
push_preference: 'all',
|
|
46
|
+
status: 'accepted',
|
|
47
|
+
source_feed: {
|
|
48
|
+
...mockFeed,
|
|
49
|
+
id: 'source-feed',
|
|
50
|
+
fid: 'user:source-feed',
|
|
51
|
+
created_by: mockUser,
|
|
52
|
+
},
|
|
53
|
+
target_feed: {
|
|
54
|
+
...mockFeed,
|
|
55
|
+
id: 'target-feed',
|
|
56
|
+
fid: 'user:target-feed',
|
|
57
|
+
created_by: mockUser,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
describe('handleFollowCreated', () => {
|
|
62
|
+
it('should return unchanged state for non-accepted follows', () => {
|
|
63
|
+
const follow: FollowResponse = {
|
|
64
|
+
...mockFollow,
|
|
65
|
+
status: 'pending',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// @ts-expect-error - we're not testing the full state here
|
|
69
|
+
const currentState: FeedState = {
|
|
70
|
+
followers: [],
|
|
71
|
+
following: [],
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const result = handleFollowCreated(
|
|
75
|
+
follow,
|
|
76
|
+
currentState,
|
|
77
|
+
'user:feed-1',
|
|
78
|
+
'user-1',
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(result.changed).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should handle when this feed follows someone', () => {
|
|
85
|
+
const follow: FollowResponse = {
|
|
86
|
+
...mockFollow,
|
|
87
|
+
source_feed: {
|
|
88
|
+
...mockFeed,
|
|
89
|
+
id: 'feed-x',
|
|
90
|
+
fid: 'user:feed-x',
|
|
91
|
+
created_by: {
|
|
92
|
+
...mockUser,
|
|
93
|
+
id: 'user-x',
|
|
94
|
+
},
|
|
95
|
+
following_count: 1,
|
|
96
|
+
},
|
|
97
|
+
target_feed: {
|
|
98
|
+
...mockFeed,
|
|
99
|
+
id: 'other-feed',
|
|
100
|
+
fid: 'user:other-feed',
|
|
101
|
+
created_by: mockUser,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// @ts-expect-error - we're not testing the full state here
|
|
106
|
+
const currentState: FeedState = {
|
|
107
|
+
following: [],
|
|
108
|
+
following_count: 0,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const result = handleFollowCreated(
|
|
112
|
+
follow,
|
|
113
|
+
currentState,
|
|
114
|
+
'user:feed-x',
|
|
115
|
+
'user-1',
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
expect(result.changed).toBe(true);
|
|
119
|
+
expect(result.data.following).toHaveLength(1);
|
|
120
|
+
expect(result.data.following?.[0]).toEqual(follow);
|
|
121
|
+
expect(result.data).toMatchObject(follow.source_feed);
|
|
122
|
+
expect(result.data.own_follows).toBeUndefined();
|
|
123
|
+
expect(result.data.following_count).toEqual(1);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should handle when someone follows this feed', () => {
|
|
127
|
+
const follow: FollowResponse = {
|
|
128
|
+
...mockFollow,
|
|
129
|
+
source_feed: {
|
|
130
|
+
...mockFeed,
|
|
131
|
+
id: 'other-feed',
|
|
132
|
+
fid: 'user:other-feed',
|
|
133
|
+
created_by: {
|
|
134
|
+
...mockUser,
|
|
135
|
+
id: 'other-user',
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
target_feed: {
|
|
139
|
+
...mockFeed,
|
|
140
|
+
id: 'feed-1',
|
|
141
|
+
fid: 'user:feed-1',
|
|
142
|
+
created_by: mockUser,
|
|
143
|
+
follower_count: 1,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// @ts-expect-error - we're not testing the full state here
|
|
148
|
+
const currentState: FeedState = {
|
|
149
|
+
followers: [],
|
|
150
|
+
follower_count: 0,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const result = handleFollowCreated(
|
|
154
|
+
follow,
|
|
155
|
+
currentState,
|
|
156
|
+
'user:feed-1',
|
|
157
|
+
'user-1',
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
expect(result.changed).toBe(true);
|
|
161
|
+
expect(result.data.followers).toHaveLength(1);
|
|
162
|
+
expect(result.data.followers?.[0]).toEqual(follow);
|
|
163
|
+
expect(result.data).toMatchObject(follow.target_feed);
|
|
164
|
+
expect(result.data.own_follows).toBeUndefined();
|
|
165
|
+
expect(result.data.follower_count).toEqual(1);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should add to own_follows when connected user is the source', () => {
|
|
169
|
+
const follow: FollowResponse = {
|
|
170
|
+
...mockFollow,
|
|
171
|
+
source_feed: {
|
|
172
|
+
...mockFeed,
|
|
173
|
+
id: 'feed-1',
|
|
174
|
+
fid: 'user:feed-1',
|
|
175
|
+
created_by: { ...mockUser, id: 'user-1' },
|
|
176
|
+
},
|
|
177
|
+
target_feed: {
|
|
178
|
+
...mockFeed,
|
|
179
|
+
id: 'feed-x',
|
|
180
|
+
fid: 'user:feed-x',
|
|
181
|
+
created_by: {
|
|
182
|
+
...mockUser,
|
|
183
|
+
id: 'user-x',
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// @ts-expect-error - we're not testing the full state here
|
|
189
|
+
const currentState: FeedState = {
|
|
190
|
+
followers: [],
|
|
191
|
+
own_follows: [],
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const result = handleFollowCreated(
|
|
195
|
+
follow,
|
|
196
|
+
currentState,
|
|
197
|
+
'user:feed-x',
|
|
198
|
+
'user-1',
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
expect(result.changed).toBe(true);
|
|
202
|
+
expect(result.data.own_follows).toHaveLength(1);
|
|
203
|
+
expect(result.data.own_follows?.[0]).toEqual(follow);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should not update followers/following when they are undefined', () => {
|
|
207
|
+
const follow: FollowResponse = {
|
|
208
|
+
...mockFollow,
|
|
209
|
+
source_feed: {
|
|
210
|
+
...mockFeed,
|
|
211
|
+
id: 'other-feed',
|
|
212
|
+
fid: 'user:other-feed',
|
|
213
|
+
created_by: mockUser,
|
|
214
|
+
},
|
|
215
|
+
target_feed: {
|
|
216
|
+
...mockFeed,
|
|
217
|
+
id: 'feed-1',
|
|
218
|
+
fid: 'user:feed-1',
|
|
219
|
+
created_by: mockUser,
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// @ts-expect-error - we're not testing the full state here
|
|
224
|
+
const currentState: FeedState = {
|
|
225
|
+
followers: undefined,
|
|
226
|
+
following: undefined,
|
|
227
|
+
own_follows: undefined,
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const result = handleFollowCreated(
|
|
231
|
+
follow,
|
|
232
|
+
currentState,
|
|
233
|
+
'user:feed-1',
|
|
234
|
+
'user-1',
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
expect(result.changed).toBe(true);
|
|
238
|
+
expect(result.data.followers).toBeUndefined();
|
|
239
|
+
expect(result.data).toMatchObject(follow.target_feed);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should add new followers to the top of existing arrays', () => {
|
|
243
|
+
const existingFollow: FollowResponse = {
|
|
244
|
+
...mockFollow,
|
|
245
|
+
source_feed: {
|
|
246
|
+
...mockFeed,
|
|
247
|
+
id: 'existing-feed',
|
|
248
|
+
fid: 'user:existing-feed',
|
|
249
|
+
created_by: mockUser,
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const follow: FollowResponse = {
|
|
254
|
+
...mockFollow,
|
|
255
|
+
source_feed: {
|
|
256
|
+
...mockFeed,
|
|
257
|
+
id: 'other-feed',
|
|
258
|
+
fid: 'user:other-feed',
|
|
259
|
+
created_by: mockUser,
|
|
260
|
+
},
|
|
261
|
+
target_feed: {
|
|
262
|
+
...mockFeed,
|
|
263
|
+
id: 'feed-1',
|
|
264
|
+
fid: 'user:feed-1',
|
|
265
|
+
created_by: mockUser,
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// @ts-expect-error - we're not testing the full state here
|
|
270
|
+
const currentState: FeedState = {
|
|
271
|
+
followers: [existingFollow],
|
|
272
|
+
following: undefined,
|
|
273
|
+
own_follows: undefined,
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const result = handleFollowCreated(
|
|
277
|
+
follow,
|
|
278
|
+
currentState,
|
|
279
|
+
'user:feed-1',
|
|
280
|
+
'user-1',
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
expect(result.changed).toBe(true);
|
|
284
|
+
expect(result.data.followers).toHaveLength(2);
|
|
285
|
+
expect(result.data.followers?.[0]).toEqual(follow);
|
|
286
|
+
expect(result.data.followers?.[1]).toEqual(existingFollow);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe('handleFollowDeleted', () => {
|
|
291
|
+
it('should handle when this feed unfollows someone', () => {
|
|
292
|
+
const existingFollow: FollowResponse = {
|
|
293
|
+
...mockFollow,
|
|
294
|
+
source_feed: {
|
|
295
|
+
...mockFeed,
|
|
296
|
+
id: 'feed-1',
|
|
297
|
+
fid: 'user:feed-1',
|
|
298
|
+
created_by: mockUser,
|
|
299
|
+
},
|
|
300
|
+
target_feed: {
|
|
301
|
+
...mockFeed,
|
|
302
|
+
id: 'other-feed',
|
|
303
|
+
fid: 'user:other-feed',
|
|
304
|
+
created_by: mockUser,
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const follow: FollowResponse = existingFollow;
|
|
309
|
+
|
|
310
|
+
// @ts-expect-error - we're not testing the full state here
|
|
311
|
+
const currentState: FeedState = {
|
|
312
|
+
following: [existingFollow],
|
|
313
|
+
following_count: 1,
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const result = handleFollowDeleted(
|
|
317
|
+
follow,
|
|
318
|
+
currentState,
|
|
319
|
+
'user:feed-1',
|
|
320
|
+
'user-1',
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
expect(result.changed).toBe(true);
|
|
324
|
+
expect(result.data.following).toHaveLength(0);
|
|
325
|
+
expect(result.data).toMatchObject(follow.source_feed);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('should handle when someone unfollows this feed', () => {
|
|
329
|
+
const existingFollow: FollowResponse = {
|
|
330
|
+
...mockFollow,
|
|
331
|
+
source_feed: {
|
|
332
|
+
...mockFeed,
|
|
333
|
+
id: 'other-feed',
|
|
334
|
+
fid: 'user:other-feed',
|
|
335
|
+
created_by: {
|
|
336
|
+
...mockUser,
|
|
337
|
+
id: 'other-user',
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
target_feed: {
|
|
341
|
+
...mockFeed,
|
|
342
|
+
id: 'feed-1',
|
|
343
|
+
fid: 'user:feed-1',
|
|
344
|
+
created_by: mockUser,
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const follow: FollowResponse = existingFollow;
|
|
349
|
+
|
|
350
|
+
// @ts-expect-error - we're not testing the full state here
|
|
351
|
+
const currentState: FeedState = {
|
|
352
|
+
followers: [existingFollow],
|
|
353
|
+
own_follows: [existingFollow],
|
|
354
|
+
following_count: 1,
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const result = handleFollowDeleted(
|
|
358
|
+
follow,
|
|
359
|
+
currentState,
|
|
360
|
+
'user:feed-1',
|
|
361
|
+
'user-1',
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
expect(result.changed).toBe(true);
|
|
365
|
+
expect(result.data.followers).toHaveLength(0);
|
|
366
|
+
expect(result.data.own_follows).toEqual(currentState.own_follows);
|
|
367
|
+
expect(result.data).toMatchObject(follow.target_feed);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('should only remove own_follows when connected user is the source', () => {
|
|
371
|
+
const existingFollow: FollowResponse = {
|
|
372
|
+
...mockFollow,
|
|
373
|
+
source_feed: {
|
|
374
|
+
...mockFeed,
|
|
375
|
+
id: 'other-feed',
|
|
376
|
+
fid: 'user:other-feed',
|
|
377
|
+
created_by: { ...mockUser, id: 'user-1' },
|
|
378
|
+
},
|
|
379
|
+
target_feed: {
|
|
380
|
+
...mockFeed,
|
|
381
|
+
id: 'feed-1',
|
|
382
|
+
fid: 'user:feed-1',
|
|
383
|
+
created_by: mockUser,
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const follow: FollowResponse = existingFollow;
|
|
388
|
+
|
|
389
|
+
// @ts-expect-error - we're not testing the full state here
|
|
390
|
+
const currentState: FeedState = {
|
|
391
|
+
followers: [existingFollow],
|
|
392
|
+
own_follows: [existingFollow],
|
|
393
|
+
following_count: 1,
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const result = handleFollowDeleted(
|
|
397
|
+
follow,
|
|
398
|
+
currentState,
|
|
399
|
+
'user:feed-1',
|
|
400
|
+
'user-1',
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
expect(result.changed).toBe(true);
|
|
404
|
+
expect(result.data.followers).toHaveLength(0);
|
|
405
|
+
expect(result.data.own_follows).toHaveLength(0);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('should not remove own_follows when connected user is not the source', () => {
|
|
409
|
+
const existingFollow: FollowResponse = {
|
|
410
|
+
...mockFollow,
|
|
411
|
+
source_feed: {
|
|
412
|
+
...mockFeed,
|
|
413
|
+
id: 'other-feed',
|
|
414
|
+
fid: 'user:other-feed',
|
|
415
|
+
created_by: { ...mockUser, id: 'other-user' },
|
|
416
|
+
},
|
|
417
|
+
target_feed: {
|
|
418
|
+
...mockFeed,
|
|
419
|
+
id: 'feed-1',
|
|
420
|
+
fid: 'user:feed-1',
|
|
421
|
+
created_by: mockUser,
|
|
422
|
+
},
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const follow: FollowResponse = existingFollow;
|
|
426
|
+
|
|
427
|
+
// @ts-expect-error - we're not testing the full state here
|
|
428
|
+
const currentState: FeedState = {
|
|
429
|
+
followers: [existingFollow],
|
|
430
|
+
own_follows: [existingFollow],
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const result = handleFollowDeleted(
|
|
434
|
+
follow,
|
|
435
|
+
currentState,
|
|
436
|
+
'user:feed-1',
|
|
437
|
+
'user-1',
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
expect(result.changed).toBe(true);
|
|
441
|
+
expect(result.data.followers).toHaveLength(0);
|
|
442
|
+
expect(result.data.own_follows).toHaveLength(1); // Should remain unchanged
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('should not update followers/following when they are undefined in delete', () => {
|
|
446
|
+
const existingFollow: FollowResponse = {
|
|
447
|
+
...mockFollow,
|
|
448
|
+
source_feed: {
|
|
449
|
+
...mockFeed,
|
|
450
|
+
id: 'other-feed',
|
|
451
|
+
fid: 'user:other-feed',
|
|
452
|
+
created_by: mockUser,
|
|
453
|
+
},
|
|
454
|
+
target_feed: {
|
|
455
|
+
...mockFeed,
|
|
456
|
+
id: 'feed-1',
|
|
457
|
+
fid: 'user:feed-1',
|
|
458
|
+
created_by: mockUser,
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
const follow: FollowResponse = existingFollow;
|
|
463
|
+
|
|
464
|
+
// @ts-expect-error - we're not testing the full state here
|
|
465
|
+
const currentState: FeedState = {
|
|
466
|
+
followers: undefined,
|
|
467
|
+
own_follows: undefined,
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const result = handleFollowDeleted(
|
|
471
|
+
follow,
|
|
472
|
+
currentState,
|
|
473
|
+
'user:feed-1',
|
|
474
|
+
'user-1',
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
expect(result.changed).toBe(true);
|
|
478
|
+
expect(result.data.followers).toBeUndefined();
|
|
479
|
+
expect(result.data.own_follows).toBeUndefined();
|
|
480
|
+
expect(result.data).toMatchObject(follow.target_feed);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('should filter out the correct follow by target feed id', () => {
|
|
484
|
+
const followToRemove: FollowResponse = {
|
|
485
|
+
...mockFollow,
|
|
486
|
+
source_feed: {
|
|
487
|
+
...mockFeed,
|
|
488
|
+
id: 'feed-1',
|
|
489
|
+
fid: 'user:feed-1',
|
|
490
|
+
created_by: mockUser,
|
|
491
|
+
},
|
|
492
|
+
target_feed: {
|
|
493
|
+
...mockFeed,
|
|
494
|
+
id: 'target-to-remove',
|
|
495
|
+
fid: 'user:target-to-remove',
|
|
496
|
+
created_by: mockUser,
|
|
497
|
+
},
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
const followToKeep: FollowResponse = {
|
|
501
|
+
...mockFollow,
|
|
502
|
+
source_feed: {
|
|
503
|
+
...mockFeed,
|
|
504
|
+
id: 'feed-1',
|
|
505
|
+
fid: 'user:feed-1',
|
|
506
|
+
created_by: mockUser,
|
|
507
|
+
},
|
|
508
|
+
target_feed: {
|
|
509
|
+
...mockFeed,
|
|
510
|
+
id: 'target-to-keep',
|
|
511
|
+
fid: 'user:target-to-keep',
|
|
512
|
+
created_by: mockUser,
|
|
513
|
+
},
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
const follow: FollowResponse = followToRemove;
|
|
517
|
+
|
|
518
|
+
// @ts-expect-error - we're not testing the full state here
|
|
519
|
+
const currentState: FeedState = {
|
|
520
|
+
following: [followToRemove, followToKeep],
|
|
521
|
+
following_count: 2,
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
const result = handleFollowDeleted(
|
|
525
|
+
follow,
|
|
526
|
+
currentState,
|
|
527
|
+
'user:feed-1',
|
|
528
|
+
'user-1',
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
expect(result.changed).toBe(true);
|
|
532
|
+
expect(result.data.following).toHaveLength(1);
|
|
533
|
+
expect(result.data.following?.[0]).toEqual(followToKeep);
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
describe('handleFollowUpdated', () => {
|
|
538
|
+
// TODO: not yet implemented
|
|
539
|
+
it.skip('should return unchanged state (no-op)', () => {
|
|
540
|
+
// @ts-expect-error - we're not testing the full state here
|
|
541
|
+
const currentState: FeedState = {
|
|
542
|
+
followers: [],
|
|
543
|
+
following: [],
|
|
544
|
+
following_count: 0,
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
const result = handleFollowUpdated(currentState);
|
|
548
|
+
|
|
549
|
+
expect(result.changed).toBe(false);
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { FeedState } from '../Feed';
|
|
2
|
+
import { FollowResponse, FeedResponse } from '../gen/models';
|
|
3
|
+
import { UpdateStateResult } from '../types-internal';
|
|
4
|
+
|
|
5
|
+
const isFeedResponse = (
|
|
6
|
+
follow: FeedResponse | { fid: string },
|
|
7
|
+
): follow is FeedResponse => {
|
|
8
|
+
return 'created_by' in follow;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const handleFollowCreated = (
|
|
12
|
+
follow: FollowResponse,
|
|
13
|
+
currentState: FeedState,
|
|
14
|
+
currentFeedId: string,
|
|
15
|
+
connectedUserId?: string,
|
|
16
|
+
): UpdateStateResult<{ data: FeedState }> => {
|
|
17
|
+
// filter non-accepted follows (the way getOrCreate does by default)
|
|
18
|
+
if (follow.status !== 'accepted') {
|
|
19
|
+
return { changed: false, data: currentState };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let newState: FeedState = { ...currentState };
|
|
23
|
+
|
|
24
|
+
// this feed followed someone
|
|
25
|
+
if (follow.source_feed.fid === currentFeedId) {
|
|
26
|
+
newState = {
|
|
27
|
+
...newState,
|
|
28
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
29
|
+
...follow.source_feed,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Only update if following array already exists
|
|
33
|
+
if (currentState.following !== undefined) {
|
|
34
|
+
newState.following = [follow, ...currentState.following];
|
|
35
|
+
}
|
|
36
|
+
} else if (
|
|
37
|
+
// someone followed this feed
|
|
38
|
+
follow.target_feed.fid === currentFeedId
|
|
39
|
+
) {
|
|
40
|
+
const source = follow.source_feed;
|
|
41
|
+
|
|
42
|
+
newState = {
|
|
43
|
+
...newState,
|
|
44
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
45
|
+
...follow.target_feed,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
if (source.created_by.id === connectedUserId) {
|
|
49
|
+
newState.own_follows = currentState.own_follows
|
|
50
|
+
? currentState.own_follows.concat(follow)
|
|
51
|
+
: [follow];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Only update if followers array already exists
|
|
55
|
+
if (currentState.followers !== undefined) {
|
|
56
|
+
newState.followers = [follow, ...currentState.followers];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { changed: true, data: newState };
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const handleFollowDeleted = (
|
|
64
|
+
follow:
|
|
65
|
+
| FollowResponse
|
|
66
|
+
| { source_feed: { fid: string }; target_feed: { fid: string } },
|
|
67
|
+
currentState: FeedState,
|
|
68
|
+
currentFeedId: string,
|
|
69
|
+
connectedUserId?: string,
|
|
70
|
+
): UpdateStateResult<{ data: FeedState }> => {
|
|
71
|
+
let newState: FeedState = { ...currentState };
|
|
72
|
+
|
|
73
|
+
// this feed unfollowed someone
|
|
74
|
+
if (follow.source_feed.fid === currentFeedId) {
|
|
75
|
+
newState = {
|
|
76
|
+
...newState,
|
|
77
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
78
|
+
...follow.source_feed,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Only update if following array already exists
|
|
82
|
+
if (currentState.following !== undefined) {
|
|
83
|
+
newState.following = currentState.following.filter(
|
|
84
|
+
(followItem) => followItem.target_feed.fid !== follow.target_feed.fid,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
} else if (
|
|
88
|
+
// someone unfollowed this feed
|
|
89
|
+
follow.target_feed.fid === currentFeedId
|
|
90
|
+
) {
|
|
91
|
+
const source = follow.source_feed;
|
|
92
|
+
|
|
93
|
+
newState = {
|
|
94
|
+
...newState,
|
|
95
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
96
|
+
...follow.target_feed,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
if (
|
|
100
|
+
isFeedResponse(source) &&
|
|
101
|
+
source.created_by.id === connectedUserId &&
|
|
102
|
+
currentState.own_follows !== undefined
|
|
103
|
+
) {
|
|
104
|
+
newState.own_follows = currentState.own_follows.filter(
|
|
105
|
+
(followItem) => followItem.source_feed.fid !== follow.source_feed.fid,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Only update if followers array already exists
|
|
110
|
+
if (currentState.followers !== undefined) {
|
|
111
|
+
newState.followers = currentState.followers.filter(
|
|
112
|
+
(followItem) => followItem.source_feed.fid !== follow.source_feed.fid,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { changed: true, data: newState };
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const handleFollowUpdated = (
|
|
121
|
+
currentState: FeedState,
|
|
122
|
+
): UpdateStateResult<{ data: FeedState }> => {
|
|
123
|
+
// For now, we'll treat follow updates as no-ops since the current implementation does
|
|
124
|
+
// This can be enhanced later if needed
|
|
125
|
+
return { changed: false, data: currentState };
|
|
126
|
+
};
|