@stream-io/feeds-client 0.1.10 → 0.1.11
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 +13 -0
- package/dist/@react-bindings/contexts/StreamFeedContext.d.ts +1 -1
- package/dist/@react-bindings/contexts/StreamFeedsContext.d.ts +1 -1
- package/dist/@react-bindings/hooks/feed-state-hooks/useComments.d.ts +1 -1
- package/dist/@react-bindings/hooks/feed-state-hooks/useFeedActivities.d.ts +1 -1
- package/dist/@react-bindings/hooks/feed-state-hooks/useFeedMetadata.d.ts +1 -1
- package/dist/@react-bindings/hooks/feed-state-hooks/useFollowers.d.ts +1 -1
- package/dist/@react-bindings/hooks/feed-state-hooks/useFollowing.d.ts +1 -1
- package/dist/@react-bindings/hooks/feed-state-hooks/useOwnCapabilities.d.ts +1 -1
- package/dist/@react-bindings/hooks/feed-state-hooks/useOwnFollows.d.ts +1 -1
- package/dist/@react-bindings/hooks/useCreateFeedsClient.d.ts +1 -1
- package/dist/@react-bindings/wrappers/StreamFeed.d.ts +1 -1
- package/dist/index-react-bindings.browser.cjs +1589 -1518
- package/dist/index-react-bindings.browser.cjs.map +1 -1
- package/dist/index-react-bindings.browser.js +1589 -1518
- package/dist/index-react-bindings.browser.js.map +1 -1
- package/dist/index-react-bindings.node.cjs +1589 -1518
- package/dist/index-react-bindings.node.cjs.map +1 -1
- package/dist/index-react-bindings.node.js +1589 -1518
- package/dist/index-react-bindings.node.js.map +1 -1
- package/dist/index.browser.cjs +1607 -1533
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.js +1605 -1534
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.node.cjs +1607 -1533
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.js +1605 -1534
- package/dist/index.node.js.map +1 -1
- package/dist/src/common/ActivitySearchSource.d.ts +1 -1
- package/dist/src/common/FeedSearchSource.d.ts +2 -2
- package/dist/src/common/Poll.d.ts +1 -1
- package/dist/src/common/UserSearchSource.d.ts +1 -1
- package/dist/src/common/real-time/StableWSConnection.d.ts +3 -3
- package/dist/src/feed/event-handlers/activity/handle-activity-added.d.ts +7 -0
- package/dist/src/feed/event-handlers/activity/handle-activity-deleted.d.ts +8 -0
- package/dist/src/feed/event-handlers/activity/handle-activity-reaction-added.d.ts +8 -0
- package/dist/src/feed/event-handlers/activity/handle-activity-reaction-deleted.d.ts +8 -0
- package/dist/src/feed/event-handlers/activity/handle-activity-removed-from-feed.d.ts +3 -0
- package/dist/src/feed/event-handlers/activity/handle-activity-updated.d.ts +8 -0
- package/dist/src/feed/event-handlers/activity/index.d.ts +6 -0
- package/dist/src/feed/event-handlers/bookmark/handle-bookmark-added.d.ts +8 -0
- package/dist/src/feed/event-handlers/bookmark/handle-bookmark-deleted.d.ts +9 -0
- package/dist/src/feed/event-handlers/bookmark/handle-bookmark-updated.d.ts +8 -0
- package/dist/src/feed/event-handlers/bookmark/index.d.ts +3 -0
- package/dist/src/feed/event-handlers/comment/handle-comment-added.d.ts +3 -0
- package/dist/src/feed/event-handlers/comment/handle-comment-deleted.d.ts +3 -0
- package/dist/src/feed/event-handlers/comment/handle-comment-reaction.d.ts +3 -0
- package/dist/src/feed/event-handlers/comment/handle-comment-updated.d.ts +3 -0
- package/dist/src/feed/event-handlers/comment/index.d.ts +4 -0
- package/dist/src/feed/event-handlers/feed/handle-feed-updated.d.ts +3 -0
- package/dist/src/feed/event-handlers/feed/index.d.ts +1 -0
- package/dist/src/feed/event-handlers/feed-member/handle-feed-member-added.d.ts +3 -0
- package/dist/src/feed/event-handlers/feed-member/handle-feed-member-removed.d.ts +3 -0
- package/dist/src/feed/event-handlers/feed-member/handle-feed-member-updated.d.ts +3 -0
- package/dist/src/feed/event-handlers/feed-member/index.d.ts +3 -0
- package/dist/src/feed/event-handlers/follow/handle-follow-created.d.ts +7 -0
- package/dist/src/feed/event-handlers/follow/handle-follow-deleted.d.ts +7 -0
- package/dist/src/feed/event-handlers/follow/handle-follow-updated.d.ts +3 -0
- package/dist/src/feed/event-handlers/follow/index.d.ts +3 -0
- package/dist/src/feed/event-handlers/index.d.ts +7 -0
- package/dist/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.d.ts +3 -0
- package/dist/src/feed/event-handlers/notification-feed/index.d.ts +1 -0
- package/dist/src/{Feed.d.ts → feed/feed.d.ts} +15 -34
- package/dist/src/feed/index.d.ts +2 -0
- package/dist/src/{FeedsClient.d.ts → feeds-client.d.ts} +6 -5
- package/dist/src/gen/models/index.d.ts +5 -0
- package/dist/src/gen-imports.d.ts +1 -1
- package/dist/src/test-utils/index.d.ts +1 -0
- package/dist/src/test-utils/response-generators.d.ts +9 -0
- package/dist/src/types-internal.d.ts +7 -0
- package/dist/src/types.d.ts +1 -1
- package/dist/src/utils/check-has-another-page.d.ts +1 -0
- package/dist/src/utils/constants.d.ts +3 -0
- package/dist/src/utils/index.d.ts +5 -0
- package/dist/src/utils/state-update-queue.d.ts +6 -0
- package/dist/src/utils/type-assertions.d.ts +7 -0
- package/dist/src/utils/unique-array-merge.d.ts +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/index.ts +2 -2
- package/package.json +2 -1
- package/src/common/ActivitySearchSource.ts +1 -1
- package/src/common/FeedSearchSource.ts +2 -2
- package/src/common/Poll.ts +1 -1
- package/src/common/UserSearchSource.ts +1 -1
- package/src/{state-updates → feed/event-handlers/activity}/activity-reaction-utils.test.ts +12 -2
- package/src/{state-updates → feed/event-handlers/activity}/activity-utils.test.ts +3 -2
- package/src/{state-updates/activity-utils.ts → feed/event-handlers/activity/handle-activity-added.ts} +16 -36
- package/src/feed/event-handlers/activity/handle-activity-deleted.ts +30 -0
- package/src/feed/event-handlers/activity/handle-activity-reaction-added.ts +67 -0
- package/src/feed/event-handlers/activity/handle-activity-reaction-deleted.ts +75 -0
- package/src/feed/event-handlers/activity/handle-activity-removed-from-feed.ts +16 -0
- package/src/feed/event-handlers/activity/handle-activity-updated.ts +47 -0
- package/src/feed/event-handlers/activity/index.ts +6 -0
- package/src/{state-updates → feed/event-handlers/bookmark}/bookmark-utils.test.ts +2 -2
- package/src/feed/event-handlers/bookmark/handle-bookmark-added.ts +63 -0
- package/src/feed/event-handlers/bookmark/handle-bookmark-deleted.ts +84 -0
- package/src/feed/event-handlers/bookmark/handle-bookmark-updated.ts +76 -0
- package/src/feed/event-handlers/bookmark/index.ts +3 -0
- package/src/feed/event-handlers/comment/handle-comment-added.ts +38 -0
- package/src/feed/event-handlers/comment/handle-comment-deleted.ts +35 -0
- package/src/feed/event-handlers/comment/handle-comment-reaction.ts +61 -0
- package/src/feed/event-handlers/comment/handle-comment-updated.ts +35 -0
- package/src/feed/event-handlers/comment/index.ts +4 -0
- package/src/feed/event-handlers/feed/handle-feed-updated.ts +9 -0
- package/src/feed/event-handlers/feed/index.ts +1 -0
- package/src/feed/event-handlers/feed-member/handle-feed-member-added.ts +31 -0
- package/src/feed/event-handlers/feed-member/handle-feed-member-removed.ts +24 -0
- package/src/feed/event-handlers/feed-member/handle-feed-member-updated.ts +40 -0
- package/src/feed/event-handlers/feed-member/index.ts +3 -0
- package/src/feed/event-handlers/follow/handle-follow-created.test.ts +246 -0
- package/src/feed/event-handlers/follow/handle-follow-created.ts +93 -0
- package/src/feed/event-handlers/follow/handle-follow-deleted.test.ts +264 -0
- package/src/feed/event-handlers/follow/handle-follow-deleted.ts +95 -0
- package/src/feed/event-handlers/follow/handle-follow-updated.test.ts +174 -0
- package/src/feed/event-handlers/follow/handle-follow-updated.ts +88 -0
- package/src/feed/event-handlers/follow/index.ts +3 -0
- package/src/feed/event-handlers/index.ts +7 -0
- package/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.ts +10 -0
- package/src/feed/event-handlers/notification-feed/index.ts +1 -0
- package/src/{Feed.ts → feed/feed.ts} +72 -483
- package/src/feed/index.ts +2 -0
- package/src/{FeedsClient.ts → feeds-client.ts} +26 -8
- package/src/gen/model-decoders/decoders.ts +7 -0
- package/src/gen/models/index.ts +10 -0
- package/src/gen-imports.ts +1 -1
- package/src/test-utils/index.ts +1 -0
- package/src/test-utils/response-generators.ts +102 -0
- package/src/types-internal.ts +11 -0
- package/src/types.ts +1 -1
- package/src/utils/check-has-another-page.ts +6 -0
- package/src/utils/constants.ts +3 -0
- package/src/utils/index.ts +5 -0
- package/src/{state-updates → utils}/state-update-queue.test.ts +6 -6
- package/src/utils/state-update-queue.ts +42 -0
- package/src/utils/type-assertions.ts +22 -0
- package/src/{utils.test.ts → utils/unique-array-merge.test.ts} +7 -3
- package/src/utils/unique-array-merge.ts +19 -0
- package/dist/src/state-updates/activity-reaction-utils.d.ts +0 -10
- package/dist/src/state-updates/activity-utils.d.ts +0 -13
- package/dist/src/state-updates/bookmark-utils.d.ts +0 -14
- package/dist/src/state-updates/follow-utils.d.ts +0 -19
- package/dist/src/state-updates/state-update-queue.d.ts +0 -15
- package/dist/src/utils.d.ts +0 -10
- package/src/state-updates/activity-reaction-utils.ts +0 -107
- package/src/state-updates/bookmark-utils.ts +0 -167
- package/src/state-updates/follow-utils.test.ts +0 -552
- package/src/state-updates/follow-utils.ts +0 -126
- package/src/state-updates/state-update-queue.ts +0 -35
- package/src/utils.ts +0 -48
- /package/dist/src/{ModerationClient.d.ts → moderation-client.d.ts} +0 -0
- /package/src/{ModerationClient.ts → moderation-client.ts} +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { Feed, FeedState } from '../../../feed';
|
|
2
|
+
import type { FollowResponse } from '../../../gen/models';
|
|
3
|
+
import type {
|
|
4
|
+
EventPayload,
|
|
5
|
+
PartializeAllBut,
|
|
6
|
+
UpdateStateResult,
|
|
7
|
+
} from '../../../types-internal';
|
|
8
|
+
import {
|
|
9
|
+
getStateUpdateQueueId,
|
|
10
|
+
shouldUpdateState,
|
|
11
|
+
} from '../../../utils';
|
|
12
|
+
|
|
13
|
+
export const updateStateFollowCreated = (
|
|
14
|
+
follow: FollowResponse,
|
|
15
|
+
currentState: FeedState,
|
|
16
|
+
currentFeedId: string,
|
|
17
|
+
connectedUserId?: string,
|
|
18
|
+
): UpdateStateResult<{ data: FeedState }> => {
|
|
19
|
+
// filter non-accepted follows (the way getOrCreate does by default)
|
|
20
|
+
if (follow.status !== 'accepted') {
|
|
21
|
+
return { changed: false, data: currentState };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let newState: FeedState = { ...currentState };
|
|
25
|
+
|
|
26
|
+
// this feed followed someone
|
|
27
|
+
if (follow.source_feed.fid === currentFeedId) {
|
|
28
|
+
newState = {
|
|
29
|
+
...newState,
|
|
30
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
31
|
+
...follow.source_feed,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Only update if following array already exists
|
|
35
|
+
if (currentState.following !== undefined) {
|
|
36
|
+
newState.following = [follow, ...currentState.following];
|
|
37
|
+
}
|
|
38
|
+
} else if (
|
|
39
|
+
// someone followed this feed
|
|
40
|
+
follow.target_feed.fid === currentFeedId
|
|
41
|
+
) {
|
|
42
|
+
const source = follow.source_feed;
|
|
43
|
+
|
|
44
|
+
newState = {
|
|
45
|
+
...newState,
|
|
46
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
47
|
+
...follow.target_feed,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (source.created_by.id === connectedUserId) {
|
|
51
|
+
newState.own_follows = currentState.own_follows
|
|
52
|
+
? currentState.own_follows.concat(follow)
|
|
53
|
+
: [follow];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Only update if followers array already exists
|
|
57
|
+
if (currentState.followers !== undefined) {
|
|
58
|
+
newState.followers = [follow, ...currentState.followers];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { changed: true, data: newState };
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export function handleFollowCreated(
|
|
66
|
+
this: Feed,
|
|
67
|
+
eventOrResponse: PartializeAllBut<
|
|
68
|
+
EventPayload<'feeds.follow.created'>,
|
|
69
|
+
'follow'
|
|
70
|
+
>,
|
|
71
|
+
) {
|
|
72
|
+
const follow = eventOrResponse.follow;
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
!shouldUpdateState({
|
|
76
|
+
stateUpdateQueueId: getStateUpdateQueueId(follow, 'created'),
|
|
77
|
+
stateUpdateQueue: this.stateUpdateQueue,
|
|
78
|
+
watch: this.currentState.watch,
|
|
79
|
+
})
|
|
80
|
+
) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const connectedUser = this.client.state.getLatestValue().connected_user;
|
|
84
|
+
const result = updateStateFollowCreated(
|
|
85
|
+
follow,
|
|
86
|
+
this.currentState,
|
|
87
|
+
this.fid,
|
|
88
|
+
connectedUser?.id,
|
|
89
|
+
);
|
|
90
|
+
if (result.changed) {
|
|
91
|
+
this.state.next(result.data);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { FollowResponse, FeedResponse, UserResponse } from '../../../gen/models';
|
|
2
|
+
import { generateFollowResponse } from '../../../test-utils';
|
|
3
|
+
import { updateStateFollowDeleted } from './handle-follow-deleted';
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
6
|
+
|
|
7
|
+
describe('handle-follow-deleted', () => {
|
|
8
|
+
describe(updateStateFollowDeleted.name, () => {
|
|
9
|
+
let mockFollow: FollowResponse;
|
|
10
|
+
let mockFeed: FeedResponse;
|
|
11
|
+
let mockUser: UserResponse;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
mockFollow = generateFollowResponse();
|
|
15
|
+
mockFeed = mockFollow.source_feed;
|
|
16
|
+
mockUser = mockFeed.created_by;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should handle when this feed unfollows someone', () => {
|
|
20
|
+
const existingFollow: FollowResponse = {
|
|
21
|
+
...mockFollow,
|
|
22
|
+
source_feed: {
|
|
23
|
+
...mockFeed,
|
|
24
|
+
id: 'feed-1',
|
|
25
|
+
fid: 'user:feed-1',
|
|
26
|
+
created_by: mockUser,
|
|
27
|
+
},
|
|
28
|
+
target_feed: {
|
|
29
|
+
...mockFeed,
|
|
30
|
+
id: 'other-feed',
|
|
31
|
+
fid: 'user:other-feed',
|
|
32
|
+
created_by: mockUser,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const follow: FollowResponse = existingFollow;
|
|
37
|
+
|
|
38
|
+
// @ts-expect-error - we're not testing the full state here
|
|
39
|
+
const currentState: FeedState = {
|
|
40
|
+
following: [existingFollow],
|
|
41
|
+
following_count: 1,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const result = updateStateFollowDeleted(
|
|
45
|
+
follow,
|
|
46
|
+
currentState,
|
|
47
|
+
'user:feed-1',
|
|
48
|
+
'user-1',
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(result.changed).toBe(true);
|
|
52
|
+
expect(result.data.following).toHaveLength(0);
|
|
53
|
+
expect(result.data).toMatchObject(follow.source_feed);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should handle when someone unfollows this feed', () => {
|
|
57
|
+
const existingFollow: FollowResponse = {
|
|
58
|
+
...mockFollow,
|
|
59
|
+
source_feed: {
|
|
60
|
+
...mockFeed,
|
|
61
|
+
id: 'other-feed',
|
|
62
|
+
fid: 'user:other-feed',
|
|
63
|
+
created_by: {
|
|
64
|
+
...mockUser,
|
|
65
|
+
id: 'other-user',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
target_feed: {
|
|
69
|
+
...mockFeed,
|
|
70
|
+
id: 'feed-1',
|
|
71
|
+
fid: 'user:feed-1',
|
|
72
|
+
created_by: mockUser,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const follow: FollowResponse = existingFollow;
|
|
77
|
+
|
|
78
|
+
// @ts-expect-error - we're not testing the full state here
|
|
79
|
+
const currentState: FeedState = {
|
|
80
|
+
followers: [existingFollow],
|
|
81
|
+
own_follows: [existingFollow],
|
|
82
|
+
following_count: 1,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const result = updateStateFollowDeleted(
|
|
86
|
+
follow,
|
|
87
|
+
currentState,
|
|
88
|
+
'user:feed-1',
|
|
89
|
+
'user-1',
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
expect(result.changed).toBe(true);
|
|
93
|
+
expect(result.data.followers).toHaveLength(0);
|
|
94
|
+
expect(result.data.own_follows).toEqual(currentState.own_follows);
|
|
95
|
+
expect(result.data).toMatchObject(follow.target_feed);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should only remove own_follows when connected user is the source', () => {
|
|
99
|
+
const existingFollow: FollowResponse = {
|
|
100
|
+
...mockFollow,
|
|
101
|
+
source_feed: {
|
|
102
|
+
...mockFeed,
|
|
103
|
+
id: 'other-feed',
|
|
104
|
+
fid: 'user:other-feed',
|
|
105
|
+
created_by: { ...mockUser, id: 'user-1' },
|
|
106
|
+
},
|
|
107
|
+
target_feed: {
|
|
108
|
+
...mockFeed,
|
|
109
|
+
id: 'feed-1',
|
|
110
|
+
fid: 'user:feed-1',
|
|
111
|
+
created_by: mockUser,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const follow: FollowResponse = existingFollow;
|
|
116
|
+
|
|
117
|
+
// @ts-expect-error - we're not testing the full state here
|
|
118
|
+
const currentState: FeedState = {
|
|
119
|
+
followers: [existingFollow],
|
|
120
|
+
own_follows: [existingFollow],
|
|
121
|
+
following_count: 1,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const result = updateStateFollowDeleted(
|
|
125
|
+
follow,
|
|
126
|
+
currentState,
|
|
127
|
+
'user:feed-1',
|
|
128
|
+
'user-1',
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
expect(result.changed).toBe(true);
|
|
132
|
+
expect(result.data.followers).toHaveLength(0);
|
|
133
|
+
expect(result.data.own_follows).toHaveLength(0);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should not remove own_follows when connected user is not the source', () => {
|
|
137
|
+
const existingFollow: FollowResponse = {
|
|
138
|
+
...mockFollow,
|
|
139
|
+
source_feed: {
|
|
140
|
+
...mockFeed,
|
|
141
|
+
id: 'other-feed',
|
|
142
|
+
fid: 'user:other-feed',
|
|
143
|
+
created_by: { ...mockUser, id: 'other-user' },
|
|
144
|
+
},
|
|
145
|
+
target_feed: {
|
|
146
|
+
...mockFeed,
|
|
147
|
+
id: 'feed-1',
|
|
148
|
+
fid: 'user:feed-1',
|
|
149
|
+
created_by: mockUser,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const follow: FollowResponse = existingFollow;
|
|
154
|
+
|
|
155
|
+
// @ts-expect-error - we're not testing the full state here
|
|
156
|
+
const currentState: FeedState = {
|
|
157
|
+
followers: [existingFollow],
|
|
158
|
+
own_follows: [existingFollow],
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const result = updateStateFollowDeleted(
|
|
162
|
+
follow,
|
|
163
|
+
currentState,
|
|
164
|
+
'user:feed-1',
|
|
165
|
+
'user-1',
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
expect(result.changed).toBe(true);
|
|
169
|
+
expect(result.data.followers).toHaveLength(0);
|
|
170
|
+
expect(result.data.own_follows).toHaveLength(1); // Should remain unchanged
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should not update followers/following when they are undefined in delete', () => {
|
|
174
|
+
const existingFollow: FollowResponse = {
|
|
175
|
+
...mockFollow,
|
|
176
|
+
source_feed: {
|
|
177
|
+
...mockFeed,
|
|
178
|
+
id: 'other-feed',
|
|
179
|
+
fid: 'user:other-feed',
|
|
180
|
+
created_by: mockUser,
|
|
181
|
+
},
|
|
182
|
+
target_feed: {
|
|
183
|
+
...mockFeed,
|
|
184
|
+
id: 'feed-1',
|
|
185
|
+
fid: 'user:feed-1',
|
|
186
|
+
created_by: mockUser,
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const follow: FollowResponse = existingFollow;
|
|
191
|
+
|
|
192
|
+
// @ts-expect-error - we're not testing the full state here
|
|
193
|
+
const currentState: FeedState = {
|
|
194
|
+
followers: undefined,
|
|
195
|
+
own_follows: undefined,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const result = updateStateFollowDeleted(
|
|
199
|
+
follow,
|
|
200
|
+
currentState,
|
|
201
|
+
'user:feed-1',
|
|
202
|
+
'user-1',
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
expect(result.changed).toBe(true);
|
|
206
|
+
expect(result.data.followers).toBeUndefined();
|
|
207
|
+
expect(result.data.own_follows).toBeUndefined();
|
|
208
|
+
expect(result.data).toMatchObject(follow.target_feed);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should filter out the correct follow by target feed id', () => {
|
|
212
|
+
const followToRemove: FollowResponse = {
|
|
213
|
+
...mockFollow,
|
|
214
|
+
source_feed: {
|
|
215
|
+
...mockFeed,
|
|
216
|
+
id: 'feed-1',
|
|
217
|
+
fid: 'user:feed-1',
|
|
218
|
+
created_by: mockUser,
|
|
219
|
+
},
|
|
220
|
+
target_feed: {
|
|
221
|
+
...mockFeed,
|
|
222
|
+
id: 'target-to-remove',
|
|
223
|
+
fid: 'user:target-to-remove',
|
|
224
|
+
created_by: mockUser,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const followToKeep: FollowResponse = {
|
|
229
|
+
...mockFollow,
|
|
230
|
+
source_feed: {
|
|
231
|
+
...mockFeed,
|
|
232
|
+
id: 'feed-1',
|
|
233
|
+
fid: 'user:feed-1',
|
|
234
|
+
created_by: mockUser,
|
|
235
|
+
},
|
|
236
|
+
target_feed: {
|
|
237
|
+
...mockFeed,
|
|
238
|
+
id: 'target-to-keep',
|
|
239
|
+
fid: 'user:target-to-keep',
|
|
240
|
+
created_by: mockUser,
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const follow: FollowResponse = followToRemove;
|
|
245
|
+
|
|
246
|
+
// @ts-expect-error - we're not testing the full state here
|
|
247
|
+
const currentState: FeedState = {
|
|
248
|
+
following: [followToRemove, followToKeep],
|
|
249
|
+
following_count: 2,
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const result = updateStateFollowDeleted(
|
|
253
|
+
follow,
|
|
254
|
+
currentState,
|
|
255
|
+
'user:feed-1',
|
|
256
|
+
'user-1',
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
expect(result.changed).toBe(true);
|
|
260
|
+
expect(result.data.following).toHaveLength(1);
|
|
261
|
+
expect(result.data.following?.[0]).toEqual(followToKeep);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Feed, FeedState } from '../../../feed';
|
|
2
|
+
import type { FollowResponse } from '../../../gen/models';
|
|
3
|
+
import type {
|
|
4
|
+
EventPayload,
|
|
5
|
+
PartializeAllBut,
|
|
6
|
+
UpdateStateResult,
|
|
7
|
+
} from '../../../types-internal';
|
|
8
|
+
|
|
9
|
+
import { getStateUpdateQueueId, shouldUpdateState } from '../../../utils';
|
|
10
|
+
|
|
11
|
+
export const updateStateFollowDeleted = (
|
|
12
|
+
follow: FollowResponse,
|
|
13
|
+
currentState: FeedState,
|
|
14
|
+
currentFeedId: string,
|
|
15
|
+
connectedUserId?: string,
|
|
16
|
+
): UpdateStateResult<{ data: FeedState }> => {
|
|
17
|
+
let newState: FeedState = { ...currentState };
|
|
18
|
+
|
|
19
|
+
// this feed unfollowed someone
|
|
20
|
+
if (follow.source_feed.fid === currentFeedId) {
|
|
21
|
+
newState = {
|
|
22
|
+
...newState,
|
|
23
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
24
|
+
...follow.source_feed,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Only update if following array already exists
|
|
28
|
+
if (currentState.following !== undefined) {
|
|
29
|
+
newState.following = currentState.following.filter(
|
|
30
|
+
(followItem) => followItem.target_feed.fid !== follow.target_feed.fid,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
} else if (
|
|
34
|
+
// someone unfollowed this feed
|
|
35
|
+
follow.target_feed.fid === currentFeedId
|
|
36
|
+
) {
|
|
37
|
+
const source = follow.source_feed;
|
|
38
|
+
|
|
39
|
+
newState = {
|
|
40
|
+
...newState,
|
|
41
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
42
|
+
...follow.target_feed,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if (
|
|
46
|
+
source.created_by.id === connectedUserId &&
|
|
47
|
+
currentState.own_follows !== undefined
|
|
48
|
+
) {
|
|
49
|
+
newState.own_follows = currentState.own_follows.filter(
|
|
50
|
+
(followItem) => followItem.source_feed.fid !== follow.source_feed.fid,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Only update if followers array already exists
|
|
55
|
+
if (currentState.followers !== undefined) {
|
|
56
|
+
newState.followers = currentState.followers.filter(
|
|
57
|
+
(followItem) => followItem.source_feed.fid !== follow.source_feed.fid,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { changed: true, data: newState };
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export function handleFollowDeleted(
|
|
66
|
+
this: Feed,
|
|
67
|
+
eventOrResponse: PartializeAllBut<
|
|
68
|
+
EventPayload<'feeds.follow.deleted'>,
|
|
69
|
+
'follow'
|
|
70
|
+
>,
|
|
71
|
+
) {
|
|
72
|
+
const follow = eventOrResponse.follow;
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
!shouldUpdateState({
|
|
76
|
+
stateUpdateQueueId: getStateUpdateQueueId(follow, 'deleted'),
|
|
77
|
+
stateUpdateQueue: this.stateUpdateQueue,
|
|
78
|
+
watch: this.currentState.watch,
|
|
79
|
+
})
|
|
80
|
+
) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const connectedUser = this.client.state.getLatestValue().connected_user;
|
|
85
|
+
const result = updateStateFollowDeleted(
|
|
86
|
+
follow,
|
|
87
|
+
this.currentState,
|
|
88
|
+
this.fid,
|
|
89
|
+
connectedUser?.id,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (result.changed) {
|
|
93
|
+
this.state.next(result.data);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { Feed } from '../../../feed';
|
|
3
|
+
import { FeedsClient } from '../../../feeds-client';
|
|
4
|
+
import { handleFollowUpdated } from './handle-follow-updated';
|
|
5
|
+
import {
|
|
6
|
+
generateFollowResponse,
|
|
7
|
+
generateFeedResponse,
|
|
8
|
+
getHumanId,
|
|
9
|
+
generateOwnUser,
|
|
10
|
+
} from '../../../test-utils/response-generators';
|
|
11
|
+
import { FollowResponse } from '../../../gen/models';
|
|
12
|
+
import { shouldUpdateState } from '../../../utils';
|
|
13
|
+
|
|
14
|
+
describe(handleFollowUpdated.name, () => {
|
|
15
|
+
let feed: Feed;
|
|
16
|
+
let client: FeedsClient;
|
|
17
|
+
let follow: FollowResponse;
|
|
18
|
+
let otherFollow: FollowResponse;
|
|
19
|
+
let ownFollow: FollowResponse;
|
|
20
|
+
let userId: string;
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
userId = getHumanId();
|
|
24
|
+
client = new FeedsClient('mock-api-key');
|
|
25
|
+
|
|
26
|
+
client.state.partialNext({
|
|
27
|
+
connected_user: generateOwnUser({ id: userId }),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const feedResponse = generateFeedResponse({
|
|
31
|
+
id: 'main',
|
|
32
|
+
group_id: 'user',
|
|
33
|
+
created_by: { id: userId },
|
|
34
|
+
});
|
|
35
|
+
feed = new Feed(client, 'user', 'main', feedResponse);
|
|
36
|
+
// Setup follows
|
|
37
|
+
follow = generateFollowResponse({
|
|
38
|
+
source_feed: generateFeedResponse({
|
|
39
|
+
id: 'main',
|
|
40
|
+
group_id: 'user',
|
|
41
|
+
created_by: { id: userId },
|
|
42
|
+
}),
|
|
43
|
+
target_feed: generateFeedResponse({
|
|
44
|
+
id: 'target',
|
|
45
|
+
group_id: 'user',
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
otherFollow = generateFollowResponse({
|
|
50
|
+
source_feed: generateFeedResponse({
|
|
51
|
+
id: 'other',
|
|
52
|
+
group_id: 'user',
|
|
53
|
+
created_by: { id: getHumanId() },
|
|
54
|
+
}),
|
|
55
|
+
target_feed: generateFeedResponse({
|
|
56
|
+
id: 'main',
|
|
57
|
+
group_id: 'user',
|
|
58
|
+
}),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
ownFollow = generateFollowResponse({
|
|
62
|
+
source_feed: generateFeedResponse({
|
|
63
|
+
id: 'other',
|
|
64
|
+
group_id: 'user',
|
|
65
|
+
created_by: { id: userId },
|
|
66
|
+
}),
|
|
67
|
+
target_feed: generateFeedResponse({
|
|
68
|
+
id: 'main',
|
|
69
|
+
group_id: 'user',
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
// Set up initial state
|
|
73
|
+
feed.state.next((currentState) => ({
|
|
74
|
+
...currentState,
|
|
75
|
+
following: [follow],
|
|
76
|
+
followers: [otherFollow],
|
|
77
|
+
own_follows: [ownFollow],
|
|
78
|
+
}));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('updates a follow in following when this feed is the source', () => {
|
|
82
|
+
const updatedFollow: FollowResponse = { ...follow, status: 'pending' };
|
|
83
|
+
|
|
84
|
+
handleFollowUpdated.call(feed, { follow: updatedFollow });
|
|
85
|
+
|
|
86
|
+
const [updatedFollowAfter] = feed.currentState.following!;
|
|
87
|
+
|
|
88
|
+
expect(updatedFollowAfter).toBe(updatedFollow);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('updates a follow in followers when this feed is the target', () => {
|
|
92
|
+
const updatedOtherFollow: FollowResponse = {
|
|
93
|
+
...otherFollow,
|
|
94
|
+
status: 'rejected',
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
handleFollowUpdated.call(feed, { follow: updatedOtherFollow });
|
|
98
|
+
|
|
99
|
+
const [updatedOtherFollowAfter] = feed.currentState.followers!;
|
|
100
|
+
|
|
101
|
+
expect(updatedOtherFollowAfter).toBe(updatedOtherFollow);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('updates a follow in own_follows when connected user is the creator', () => {
|
|
105
|
+
const updatedOwnFollow: FollowResponse = {
|
|
106
|
+
...ownFollow,
|
|
107
|
+
status: 'pending',
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
handleFollowUpdated.call(feed, { follow: updatedOwnFollow });
|
|
111
|
+
|
|
112
|
+
const [ownFollowAfter] = feed.currentState.own_follows!;
|
|
113
|
+
|
|
114
|
+
expect(ownFollowAfter).toBe(updatedOwnFollow);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('does not update if follow is not found', () => {
|
|
118
|
+
const unrelatedFollow = generateFollowResponse();
|
|
119
|
+
|
|
120
|
+
const stateBefore = feed.currentState;
|
|
121
|
+
|
|
122
|
+
handleFollowUpdated.call(feed, { follow: unrelatedFollow });
|
|
123
|
+
|
|
124
|
+
const stateAfter = feed.currentState;
|
|
125
|
+
|
|
126
|
+
expect(stateAfter.own_follows).toBe(stateBefore.own_follows);
|
|
127
|
+
expect(stateAfter.followers).toBe(stateBefore.followers);
|
|
128
|
+
expect(stateAfter.follower_count).toBe(stateBefore.follower_count);
|
|
129
|
+
expect(stateAfter.following).toBe(stateBefore.following);
|
|
130
|
+
expect(stateAfter.following_count).toBe(stateBefore.following_count);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe(`${shouldUpdateState.name} integration`, () => {
|
|
134
|
+
it(`skips update if ${shouldUpdateState.name} returns false`, () => {
|
|
135
|
+
// Prepare feed, set as watched
|
|
136
|
+
feed.state.partialNext({ watch: true });
|
|
137
|
+
|
|
138
|
+
const updatedFollow: FollowResponse = { ...follow, status: 'pending' };
|
|
139
|
+
|
|
140
|
+
// Call once to populate queue
|
|
141
|
+
handleFollowUpdated.call(feed, { follow: updatedFollow });
|
|
142
|
+
|
|
143
|
+
// Call again, should be skipped
|
|
144
|
+
const stateBefore = feed.currentState;
|
|
145
|
+
handleFollowUpdated.call(feed, {
|
|
146
|
+
follow: { ...updatedFollow, status: 'accepted' },
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// State should not change
|
|
150
|
+
const stateAfter = feed.currentState;
|
|
151
|
+
expect(stateAfter).toEqual(stateBefore);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('allows update again after clearing stateUpdateQueue', () => {
|
|
155
|
+
const updatedFollow: FollowResponse = { ...follow, status: 'pending' };
|
|
156
|
+
|
|
157
|
+
handleFollowUpdated.call(feed, { follow: updatedFollow });
|
|
158
|
+
|
|
159
|
+
// Clear the queue
|
|
160
|
+
(feed as any).stateUpdateQueue.clear();
|
|
161
|
+
|
|
162
|
+
// Now update should be allowed
|
|
163
|
+
handleFollowUpdated.call(feed, {
|
|
164
|
+
follow: { ...updatedFollow, status: 'accepted' },
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const [updatedFollowAfter] = feed.currentState.following!;
|
|
168
|
+
|
|
169
|
+
expect(updatedFollowAfter).toMatchObject({
|
|
170
|
+
status: 'accepted',
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Feed, FeedState } from '../../../feed';
|
|
2
|
+
import {
|
|
3
|
+
getStateUpdateQueueId,
|
|
4
|
+
shouldUpdateState,
|
|
5
|
+
} from '../../../utils';
|
|
6
|
+
import { EventPayload, PartializeAllBut } from '../../../types-internal';
|
|
7
|
+
|
|
8
|
+
export function handleFollowUpdated(
|
|
9
|
+
this: Feed,
|
|
10
|
+
eventOrResponse: PartializeAllBut<
|
|
11
|
+
EventPayload<'feeds.follow.updated'>,
|
|
12
|
+
'follow'
|
|
13
|
+
>,
|
|
14
|
+
) {
|
|
15
|
+
const follow = eventOrResponse.follow;
|
|
16
|
+
const connectedUserId = this.client.state.getLatestValue().connected_user?.id;
|
|
17
|
+
const currentFeedId = this.fid;
|
|
18
|
+
|
|
19
|
+
if (
|
|
20
|
+
!shouldUpdateState({
|
|
21
|
+
stateUpdateQueueId: getStateUpdateQueueId(follow, 'updated'),
|
|
22
|
+
stateUpdateQueue: this.stateUpdateQueue,
|
|
23
|
+
watch: this.currentState.watch,
|
|
24
|
+
})
|
|
25
|
+
) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.state.next((currentState) => {
|
|
30
|
+
let newState: FeedState | undefined;
|
|
31
|
+
|
|
32
|
+
// this feed followed someone
|
|
33
|
+
if (follow.source_feed.fid === currentFeedId) {
|
|
34
|
+
newState ??= {
|
|
35
|
+
...currentState,
|
|
36
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
37
|
+
...follow.source_feed,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const index =
|
|
41
|
+
currentState.following?.findIndex(
|
|
42
|
+
(f) => f.target_feed.fid === follow.target_feed.fid,
|
|
43
|
+
) ?? -1;
|
|
44
|
+
|
|
45
|
+
if (index >= 0) {
|
|
46
|
+
newState.following = [...newState.following!];
|
|
47
|
+
newState.following[index] = follow;
|
|
48
|
+
}
|
|
49
|
+
} else if (
|
|
50
|
+
// someone followed this feed
|
|
51
|
+
follow.target_feed.fid === currentFeedId
|
|
52
|
+
) {
|
|
53
|
+
const source = follow.source_feed;
|
|
54
|
+
|
|
55
|
+
newState ??= {
|
|
56
|
+
...currentState,
|
|
57
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
58
|
+
...follow.target_feed,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
source.created_by.id === connectedUserId &&
|
|
63
|
+
currentState.own_follows
|
|
64
|
+
) {
|
|
65
|
+
const index = currentState.own_follows.findIndex(
|
|
66
|
+
(f) => f.source_feed.fid === follow.source_feed.fid,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (index >= 0) {
|
|
70
|
+
newState.own_follows = [...currentState.own_follows];
|
|
71
|
+
newState.own_follows[index] = follow;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const index =
|
|
76
|
+
currentState.followers?.findIndex(
|
|
77
|
+
(f) => f.source_feed.fid === follow.source_feed.fid,
|
|
78
|
+
) ?? -1;
|
|
79
|
+
|
|
80
|
+
if (index >= 0) {
|
|
81
|
+
newState.followers = [...newState.followers!];
|
|
82
|
+
newState.followers[index] = follow;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return newState ?? currentState;
|
|
87
|
+
});
|
|
88
|
+
}
|