@stream-io/feeds-client 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/@react-bindings/hooks/feed-state-hooks/index.ts +4 -0
  2. package/CHANGELOG.md +16 -0
  3. package/dist/@react-bindings/hooks/feed-state-hooks/index.d.ts +4 -0
  4. package/dist/@react-bindings/hooks/feed-state-hooks/useAggregatedActivities.d.ts +11 -0
  5. package/dist/@react-bindings/hooks/feed-state-hooks/useIsAggregatedActivityRead.d.ts +6 -0
  6. package/dist/@react-bindings/hooks/feed-state-hooks/useIsAggregatedActivitySeen.d.ts +6 -0
  7. package/dist/@react-bindings/hooks/feed-state-hooks/useNotificationStatus.d.ts +13 -0
  8. package/dist/@react-bindings/wrappers/StreamFeed.d.ts +1 -1
  9. package/dist/index-react-bindings.browser.cjs +505 -222
  10. package/dist/index-react-bindings.browser.cjs.map +1 -1
  11. package/dist/index-react-bindings.browser.js +502 -223
  12. package/dist/index-react-bindings.browser.js.map +1 -1
  13. package/dist/index-react-bindings.node.cjs +505 -222
  14. package/dist/index-react-bindings.node.cjs.map +1 -1
  15. package/dist/index-react-bindings.node.js +502 -223
  16. package/dist/index-react-bindings.node.js.map +1 -1
  17. package/dist/index.browser.cjs +440 -205
  18. package/dist/index.browser.cjs.map +1 -1
  19. package/dist/index.browser.js +440 -206
  20. package/dist/index.browser.js.map +1 -1
  21. package/dist/index.node.cjs +440 -205
  22. package/dist/index.node.cjs.map +1 -1
  23. package/dist/index.node.js +440 -206
  24. package/dist/index.node.js.map +1 -1
  25. package/dist/src/feed/event-handlers/activity/handle-activity-deleted.d.ts +12 -3
  26. package/dist/src/feed/event-handlers/activity/handle-activity-marked.d.ts +11 -0
  27. package/dist/src/feed/event-handlers/activity/handle-activity-pinned.d.ts +3 -0
  28. package/dist/src/feed/event-handlers/activity/handle-activity-reaction-added.d.ts +10 -6
  29. package/dist/src/feed/event-handlers/activity/handle-activity-reaction-deleted.d.ts +10 -6
  30. package/dist/src/feed/event-handlers/activity/handle-activity-unpinned.d.ts +3 -0
  31. package/dist/src/feed/event-handlers/activity/handle-activity-updated.d.ts +7 -3
  32. package/dist/src/feed/event-handlers/activity/index.d.ts +1 -0
  33. package/dist/src/feed/event-handlers/bookmark/handle-bookmark-added.d.ts +10 -6
  34. package/dist/src/feed/event-handlers/bookmark/handle-bookmark-deleted.d.ts +10 -6
  35. package/dist/src/feed/event-handlers/bookmark/handle-bookmark-updated.d.ts +10 -6
  36. package/dist/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.d.ts +8 -1
  37. package/dist/src/feed/feed.d.ts +2 -2
  38. package/dist/src/gen/models/index.d.ts +36 -1
  39. package/dist/src/test-utils/response-generators.d.ts +66 -1
  40. package/dist/src/utils/index.d.ts +1 -0
  41. package/dist/src/utils/update-entity-in-array.d.ts +27 -0
  42. package/dist/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +1 -1
  44. package/src/feed/event-handlers/activity/activity-marked-utils.test.ts +208 -0
  45. package/src/feed/event-handlers/activity/activity-reaction-utils.test.ts +108 -96
  46. package/src/feed/event-handlers/activity/activity-utils.test.ts +84 -122
  47. package/src/feed/event-handlers/activity/handle-activity-deleted.ts +43 -10
  48. package/src/feed/event-handlers/activity/handle-activity-marked.ts +68 -0
  49. package/src/feed/event-handlers/activity/handle-activity-pinned.test.ts +60 -0
  50. package/src/feed/event-handlers/activity/handle-activity-pinned.ts +30 -0
  51. package/src/feed/event-handlers/activity/handle-activity-reaction-added.test.ts +157 -0
  52. package/src/feed/event-handlers/activity/handle-activity-reaction-added.ts +82 -40
  53. package/src/feed/event-handlers/activity/handle-activity-reaction-deleted.test.ts +200 -0
  54. package/src/feed/event-handlers/activity/handle-activity-reaction-deleted.ts +89 -51
  55. package/src/feed/event-handlers/activity/handle-activity-unpinned.test.ts +95 -0
  56. package/src/feed/event-handlers/activity/handle-activity-unpinned.ts +30 -0
  57. package/src/feed/event-handlers/activity/handle-activity-updated.test.ts +115 -0
  58. package/src/feed/event-handlers/activity/handle-activity-updated.ts +73 -35
  59. package/src/feed/event-handlers/activity/index.ts +2 -1
  60. package/src/feed/event-handlers/bookmark/bookmark-utils.test.ts +121 -109
  61. package/src/feed/event-handlers/bookmark/handle-bookmark-added.test.ts +178 -0
  62. package/src/feed/event-handlers/bookmark/handle-bookmark-added.ts +82 -39
  63. package/src/feed/event-handlers/bookmark/handle-bookmark-deleted.test.ts +188 -0
  64. package/src/feed/event-handlers/bookmark/handle-bookmark-deleted.ts +86 -48
  65. package/src/feed/event-handlers/bookmark/handle-bookmark-updated.test.ts +196 -0
  66. package/src/feed/event-handlers/bookmark/handle-bookmark-updated.ts +83 -44
  67. package/src/feed/event-handlers/comment/handle-comment-added.test.ts +147 -0
  68. package/src/feed/event-handlers/comment/handle-comment-deleted.test.ts +133 -0
  69. package/src/feed/event-handlers/comment/handle-comment-deleted.ts +24 -10
  70. package/src/feed/event-handlers/comment/handle-comment-reaction.test.ts +315 -0
  71. package/src/feed/event-handlers/comment/handle-comment-updated.test.ts +131 -0
  72. package/src/feed/event-handlers/follow/handle-follow-created.test.ts +7 -7
  73. package/src/feed/event-handlers/follow/handle-follow-deleted.test.ts +2 -2
  74. package/src/feed/event-handlers/follow/handle-follow-updated.test.ts +1 -1
  75. package/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.test.ts +120 -0
  76. package/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.ts +47 -3
  77. package/src/feed/feed.ts +4 -2
  78. package/src/gen/model-decoders/decoders.ts +14 -1
  79. package/src/gen/models/index.ts +73 -2
  80. package/src/gen/moderation/ModerationApi.ts +1 -0
  81. package/src/test-utils/response-generators.ts +383 -0
  82. package/src/utils/index.ts +1 -0
  83. package/src/utils/update-entity-in-array.ts +51 -0
@@ -0,0 +1,178 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { Feed } from '../../../feed';
3
+ import { FeedsClient } from '../../../feeds-client';
4
+ import { handleBookmarkAdded } from './handle-bookmark-added';
5
+ import {
6
+ generateActivityPinResponse,
7
+ generateActivityResponse,
8
+ generateFeedResponse,
9
+ generateOwnUser,
10
+ getHumanId,
11
+ generateFeedReactionResponse,
12
+ generateBookmarkAddedEvent,
13
+ } from '../../../test-utils/response-generators';
14
+
15
+ describe(handleBookmarkAdded.name, () => {
16
+ let feed: Feed;
17
+ let client: FeedsClient;
18
+ let currentUserId: string;
19
+
20
+ beforeEach(() => {
21
+ client = new FeedsClient('mock-api-key');
22
+ currentUserId = getHumanId();
23
+ client.state.partialNext({
24
+ connected_user: generateOwnUser({ id: currentUserId }),
25
+ });
26
+ const feedResponse = generateFeedResponse({
27
+ id: 'main',
28
+ group_id: 'user',
29
+ created_by: { id: currentUserId },
30
+ });
31
+ feed = new Feed(
32
+ client,
33
+ feedResponse.group_id,
34
+ feedResponse.id,
35
+ feedResponse,
36
+ );
37
+ });
38
+
39
+ it('adds a bookmark for the current user and updates activities', () => {
40
+ const event = generateBookmarkAddedEvent({
41
+ bookmark: {
42
+ activity: {
43
+ own_reactions: [],
44
+ bookmark_count: 1,
45
+ },
46
+ user: { id: currentUserId },
47
+ },
48
+ });
49
+ const activity = generateActivityResponse({
50
+ id: event.bookmark.activity.id,
51
+ bookmark_count: 0,
52
+ own_bookmarks: [],
53
+ own_reactions: [generateFeedReactionResponse()],
54
+ });
55
+ const activityPin = generateActivityPinResponse({
56
+ activity: { ...activity },
57
+ });
58
+ feed.state.partialNext({
59
+ activities: [activity],
60
+ pinned_activities: [activityPin],
61
+ });
62
+
63
+ const stateBefore = feed.currentState;
64
+ expect(stateBefore.activities![0].own_bookmarks).toHaveLength(0);
65
+ expect(
66
+ stateBefore.pinned_activities![0].activity.own_bookmarks,
67
+ ).toHaveLength(0);
68
+ expect(stateBefore.activities![0].bookmark_count).toBe(0);
69
+ expect(stateBefore.pinned_activities![0].activity.bookmark_count).toBe(
70
+ 0,
71
+ );
72
+
73
+ handleBookmarkAdded.call(feed, event);
74
+
75
+ const stateAfter = feed.currentState;
76
+ expect(stateAfter.activities![0].own_bookmarks).toHaveLength(1);
77
+ expect(
78
+ stateAfter.pinned_activities![0].activity.own_bookmarks,
79
+ ).toHaveLength(1);
80
+ expect(stateAfter.activities![0].own_bookmarks).toContain(event.bookmark);
81
+ expect(stateAfter.pinned_activities![0].activity.own_bookmarks).toContain(
82
+ event.bookmark,
83
+ );
84
+ expect(stateAfter.activities![0].own_reactions).toBe(
85
+ stateBefore.activities![0].own_reactions,
86
+ );
87
+ expect(stateAfter.pinned_activities![0].activity.own_reactions).toBe(
88
+ stateBefore.pinned_activities![0].activity.own_reactions,
89
+ );
90
+ expect(stateAfter.activities![0].bookmark_count).toBe(1);
91
+ expect(stateAfter.pinned_activities![0].activity.bookmark_count).toBe(1);
92
+ });
93
+
94
+ it('does not add to own_bookmarks if bookmark is from another user but still updates activity', () => {
95
+ const event = generateBookmarkAddedEvent({
96
+ bookmark: {
97
+ activity: {
98
+ own_reactions: [],
99
+ bookmark_count: 1,
100
+ },
101
+ user: { id: 'other-user-id' },
102
+ },
103
+ });
104
+ const activity = generateActivityResponse({
105
+ id: event.bookmark.activity.id,
106
+ bookmark_count: 0,
107
+ own_bookmarks: [],
108
+ own_reactions: [generateFeedReactionResponse()],
109
+ });
110
+ const activityPin = generateActivityPinResponse({
111
+ activity: { ...activity },
112
+ });
113
+ feed.state.partialNext({
114
+ activities: [activity],
115
+ pinned_activities: [activityPin],
116
+ });
117
+
118
+ const stateBefore = feed.currentState;
119
+ expect(stateBefore.activities![0].own_bookmarks).toHaveLength(0);
120
+ expect(
121
+ stateBefore.pinned_activities![0].activity.own_bookmarks,
122
+ ).toHaveLength(0);
123
+ expect(stateBefore.activities![0].bookmark_count).toBe(0);
124
+ expect(stateBefore.pinned_activities![0].activity.bookmark_count).toBe(
125
+ 0,
126
+ );
127
+
128
+ handleBookmarkAdded.call(feed, event);
129
+
130
+ const stateAfter = feed.currentState;
131
+ expect(stateAfter.activities![0].own_bookmarks).toBe(
132
+ stateBefore.activities![0].own_bookmarks,
133
+ );
134
+ expect(stateAfter.pinned_activities![0].activity.own_bookmarks).toBe(
135
+ stateBefore.pinned_activities![0].activity.own_bookmarks,
136
+ );
137
+ expect(stateAfter.activities![0].own_reactions).toBe(
138
+ stateBefore.activities![0].own_reactions,
139
+ );
140
+ expect(stateAfter.pinned_activities![0].activity.own_reactions).toBe(
141
+ stateBefore.pinned_activities![0].activity.own_reactions,
142
+ );
143
+ expect(stateAfter.activities![0].bookmark_count).toBe(1);
144
+ expect(stateAfter.pinned_activities![0].activity.bookmark_count).toBe(1);
145
+ });
146
+
147
+ it('does nothing if activity is not found', () => {
148
+ const event = generateBookmarkAddedEvent({
149
+ bookmark: {
150
+ activity: {
151
+ own_reactions: [],
152
+ bookmark_count: 1,
153
+ },
154
+ user: { id: 'other-user-id' },
155
+ },
156
+ });
157
+ const activity = generateActivityResponse({
158
+ id: 'unrelated-activity-id',
159
+ bookmark_count: 0,
160
+ own_bookmarks: [],
161
+ own_reactions: [generateFeedReactionResponse()],
162
+ });
163
+ const activityPin = generateActivityPinResponse({
164
+ activity: { ...activity },
165
+ });
166
+ feed.state.partialNext({
167
+ activities: [activity],
168
+ pinned_activities: [activityPin],
169
+ });
170
+
171
+ const stateBefore = feed.currentState;
172
+
173
+ handleBookmarkAdded.call(feed, event);
174
+
175
+ const stateAfter = feed.currentState;
176
+ expect(stateAfter).toBe(stateBefore);
177
+ });
178
+ });
@@ -1,63 +1,106 @@
1
1
  import type { Feed } from '../../../feed';
2
- import type { ActivityResponse, BookmarkAddedEvent } from '../../../gen/models';
3
- import type { EventPayload, UpdateStateResult } from '../../../types-internal';
2
+ import type {
3
+ ActivityPinResponse,
4
+ ActivityResponse,
5
+ BookmarkAddedEvent,
6
+ } from '../../../gen/models';
7
+ import type { EventPayload } from '../../../types-internal';
8
+ import { updateEntityInArray } from '../../../utils';
4
9
 
5
- import { updateActivityInState } from '../activity';
10
+ const sharedUpdateActivity = ({
11
+ currentActivity,
12
+ event,
13
+ eventBelongsToCurrentUser,
14
+ }: {
15
+ currentActivity: ActivityResponse;
16
+ event: BookmarkAddedEvent;
17
+ eventBelongsToCurrentUser: boolean;
18
+ }): ActivityResponse => {
19
+ let newOwnBookmarks = currentActivity.own_bookmarks;
6
20
 
7
- export const addBookmarkToActivity = (
8
- event: BookmarkAddedEvent,
9
- activity: ActivityResponse,
10
- isCurrentUser: boolean,
11
- ): UpdateStateResult<ActivityResponse> => {
12
- // Update own_bookmarks if the bookmark is from the current user
13
- const ownBookmarks = [...(activity.own_bookmarks || [])];
14
- if (isCurrentUser) {
15
- ownBookmarks.push(event.bookmark);
21
+ if (eventBelongsToCurrentUser) {
22
+ newOwnBookmarks = [...newOwnBookmarks, event.bookmark];
16
23
  }
17
24
 
18
25
  return {
19
- ...activity,
20
- own_bookmarks: ownBookmarks,
21
- changed: true,
26
+ ...event.bookmark.activity,
27
+ own_bookmarks: newOwnBookmarks,
28
+ own_reactions: currentActivity.own_reactions,
22
29
  };
23
30
  };
24
31
 
25
32
  export const addBookmarkToActivities = (
26
33
  event: BookmarkAddedEvent,
27
34
  activities: ActivityResponse[] | undefined,
28
- isCurrentUser: boolean,
29
- ): UpdateStateResult<{ activities: ActivityResponse[] }> => {
30
- if (!activities) {
31
- return { changed: false, activities: [] };
32
- }
35
+ eventBelongsToCurrentUser: boolean,
36
+ ) =>
37
+ updateEntityInArray({
38
+ entities: activities,
39
+ matcher: (activity) => activity.id === event.bookmark.activity.id,
40
+ updater: (matchedActivity) =>
41
+ sharedUpdateActivity({
42
+ currentActivity: matchedActivity,
43
+ event,
44
+ eventBelongsToCurrentUser,
45
+ }),
46
+ });
33
47
 
34
- const activityIndex = activities.findIndex(
35
- (a) => a.id === event.bookmark.activity.id,
36
- );
37
- if (activityIndex === -1) {
38
- return { changed: false, activities };
39
- }
48
+ export const addBookmarkToPinnedActivities = (
49
+ event: BookmarkAddedEvent,
50
+ pinnedActivities: ActivityPinResponse[] | undefined,
51
+ eventBelongsToCurrentUser: boolean,
52
+ ) =>
53
+ updateEntityInArray({
54
+ entities: pinnedActivities,
55
+ matcher: (pinnedActivity) =>
56
+ pinnedActivity.activity.id === event.bookmark.activity.id,
57
+ updater: (matchedPinnedActivity) => {
58
+ const newActivity = sharedUpdateActivity({
59
+ currentActivity: matchedPinnedActivity.activity,
60
+ event,
61
+ eventBelongsToCurrentUser,
62
+ });
40
63
 
41
- const activity = activities[activityIndex];
42
- const updatedActivity = addBookmarkToActivity(event, activity, isCurrentUser);
43
- return updateActivityInState(updatedActivity, activities, true);
44
- };
64
+ if (newActivity === matchedPinnedActivity.activity) {
65
+ return matchedPinnedActivity;
66
+ }
67
+
68
+ return {
69
+ ...matchedPinnedActivity,
70
+ activity: newActivity,
71
+ };
72
+ },
73
+ });
45
74
 
46
75
  export function handleBookmarkAdded(
47
76
  this: Feed,
48
77
  event: EventPayload<'feeds.bookmark.added'>,
49
78
  ) {
50
- const currentActivities = this.currentState.activities;
79
+ const {
80
+ activities: currentActivities,
81
+ pinned_activities: currentPinnedActivities,
82
+ } = this.currentState;
51
83
  const { connected_user: connectedUser } = this.client.state.getLatestValue();
52
- const isCurrentUser = event.bookmark.user.id === connectedUser?.id;
84
+ const eventBelongsToCurrentUser =
85
+ event.bookmark.user.id === connectedUser?.id;
53
86
 
54
- const result = addBookmarkToActivities(
55
- event,
56
- currentActivities,
57
- isCurrentUser,
58
- );
87
+ const [result1, result2] = [
88
+ addBookmarkToActivities(
89
+ event,
90
+ currentActivities,
91
+ eventBelongsToCurrentUser,
92
+ ),
93
+ addBookmarkToPinnedActivities(
94
+ event,
95
+ currentPinnedActivities,
96
+ eventBelongsToCurrentUser,
97
+ ),
98
+ ];
59
99
 
60
- if (result.changed) {
61
- this.state.partialNext({ activities: result.activities });
100
+ if (result1.changed || result2.changed) {
101
+ this.state.partialNext({
102
+ activities: result1.entities,
103
+ pinned_activities: result2.entities,
104
+ });
62
105
  }
63
106
  }
@@ -0,0 +1,188 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { Feed } from '../../../feed';
3
+ import { FeedsClient } from '../../../feeds-client';
4
+ import { handleBookmarkDeleted } from './handle-bookmark-deleted';
5
+ import {
6
+ generateActivityPinResponse,
7
+ generateActivityResponse,
8
+ generateFeedResponse,
9
+ generateOwnUser,
10
+ getHumanId,
11
+ generateFeedReactionResponse,
12
+ generateBookmarkDeletedEvent,
13
+ generateBookmarkResponse,
14
+ } from '../../../test-utils/response-generators';
15
+
16
+ describe(handleBookmarkDeleted.name, () => {
17
+ let feed: Feed;
18
+ let client: FeedsClient;
19
+ let currentUserId: string;
20
+
21
+ beforeEach(() => {
22
+ client = new FeedsClient('mock-api-key');
23
+ currentUserId = getHumanId();
24
+ client.state.partialNext({
25
+ connected_user: generateOwnUser({ id: currentUserId }),
26
+ });
27
+ const feedResponse = generateFeedResponse({
28
+ id: 'main',
29
+ group_id: 'user',
30
+ created_by: { id: currentUserId },
31
+ });
32
+ feed = new Feed(
33
+ client,
34
+ feedResponse.group_id,
35
+ feedResponse.id,
36
+ feedResponse,
37
+ );
38
+ });
39
+
40
+ it('removes a bookmark for the current user and updates activities', () => {
41
+ const event = generateBookmarkDeletedEvent({
42
+ bookmark: {
43
+ activity: {
44
+ own_reactions: [],
45
+ bookmark_count: 0,
46
+ },
47
+ user: { id: currentUserId },
48
+ },
49
+ });
50
+ const activity = generateActivityResponse({
51
+ id: event.bookmark.activity.id,
52
+ bookmark_count: 1,
53
+ own_bookmarks: [
54
+ generateBookmarkResponse({
55
+ activity: { id: event.bookmark.activity.id },
56
+ user: { id: currentUserId },
57
+ }),
58
+ ],
59
+ own_reactions: [generateFeedReactionResponse()],
60
+ });
61
+ const activityPin = generateActivityPinResponse({
62
+ activity: { ...activity },
63
+ });
64
+ feed.state.partialNext({
65
+ activities: [activity],
66
+ pinned_activities: [activityPin],
67
+ });
68
+
69
+ const stateBefore = feed.currentState;
70
+ expect(stateBefore.activities![0].own_bookmarks).toHaveLength(1);
71
+ expect(
72
+ stateBefore.pinned_activities![0].activity.own_bookmarks,
73
+ ).toHaveLength(1);
74
+ expect(stateBefore.activities![0].bookmark_count).toBe(1);
75
+ expect(stateBefore.pinned_activities![0].activity.bookmark_count).toBe(
76
+ 1,
77
+ );
78
+
79
+ handleBookmarkDeleted.call(feed, event);
80
+
81
+ const stateAfter = feed.currentState;
82
+ expect(stateAfter.activities![0].own_bookmarks).toHaveLength(0);
83
+ expect(
84
+ stateAfter.pinned_activities![0].activity.own_bookmarks,
85
+ ).toHaveLength(0);
86
+ expect(stateAfter.activities![0].own_reactions).toBe(
87
+ stateBefore.activities![0].own_reactions,
88
+ );
89
+ expect(stateAfter.pinned_activities![0].activity.own_reactions).toBe(
90
+ stateBefore.pinned_activities![0].activity.own_reactions,
91
+ );
92
+ expect(stateAfter.activities![0].bookmark_count).toBe(0);
93
+ expect(stateAfter.pinned_activities![0].activity.bookmark_count).toBe(0);
94
+ });
95
+
96
+ it('does not remove from own_bookmarks if bookmark is from another user but still updates activity', () => {
97
+ const event = generateBookmarkDeletedEvent({
98
+ bookmark: {
99
+ activity: {
100
+ own_reactions: [],
101
+ bookmark_count: 0,
102
+ },
103
+ user: { id: 'other-user-id' },
104
+ },
105
+ });
106
+ const activity = generateActivityResponse({
107
+ id: event.bookmark.activity.id,
108
+ bookmark_count: 1,
109
+ own_bookmarks: [
110
+ generateBookmarkResponse({
111
+ activity: { id: event.bookmark.activity.id },
112
+ user: { id: currentUserId },
113
+ }),
114
+ ],
115
+ own_reactions: [generateFeedReactionResponse()],
116
+ });
117
+ const activityPin = generateActivityPinResponse({
118
+ activity: { ...activity },
119
+ });
120
+ feed.state.partialNext({
121
+ activities: [activity],
122
+ pinned_activities: [activityPin],
123
+ });
124
+
125
+ const stateBefore = feed.currentState;
126
+ expect(stateBefore.activities![0].own_bookmarks).toHaveLength(1);
127
+ expect(
128
+ stateBefore.pinned_activities![0].activity.own_bookmarks,
129
+ ).toHaveLength(1);
130
+ expect(stateBefore.activities![0].bookmark_count).toBe(1);
131
+ expect(stateBefore.pinned_activities![0].activity.bookmark_count).toBe(
132
+ 1,
133
+ );
134
+
135
+ handleBookmarkDeleted.call(feed, event);
136
+
137
+ const stateAfter = feed.currentState;
138
+ expect(stateAfter.activities![0].own_bookmarks).toBe(
139
+ stateBefore.activities![0].own_bookmarks,
140
+ );
141
+ expect(stateAfter.pinned_activities![0].activity.own_bookmarks).toBe(
142
+ stateBefore.pinned_activities![0].activity.own_bookmarks,
143
+ );
144
+ expect(stateAfter.activities![0].own_reactions).toBe(
145
+ stateBefore.activities![0].own_reactions,
146
+ );
147
+ expect(stateAfter.pinned_activities![0].activity.own_reactions).toBe(
148
+ stateBefore.pinned_activities![0].activity.own_reactions,
149
+ );
150
+ expect(stateAfter.activities![0].bookmark_count).toBe(0);
151
+ expect(stateAfter.pinned_activities![0].activity.bookmark_count).toBe(0);
152
+ });
153
+
154
+ it('does nothing if activity is not found', () => {
155
+ const event = generateBookmarkDeletedEvent({
156
+ bookmark: {
157
+ activity: {
158
+ own_reactions: [],
159
+ bookmark_count: 0,
160
+ },
161
+ user: { id: currentUserId },
162
+ },
163
+ });
164
+ const activity = generateActivityResponse({
165
+ id: 'another-activity-id',
166
+ bookmark_count: 1,
167
+ own_bookmarks: [
168
+ generateBookmarkResponse({
169
+ activity: { id: 'another-activity-id', },
170
+ user: { id: currentUserId },
171
+ }),
172
+ ],
173
+ own_reactions: [generateFeedReactionResponse()],
174
+ });
175
+ const activityPin = generateActivityPinResponse({
176
+ activity: { ...activity },
177
+ });
178
+ feed.state.partialNext({
179
+ activities: [activity],
180
+ pinned_activities: [activityPin],
181
+ });
182
+
183
+ const stateBefore = feed.currentState;
184
+ handleBookmarkDeleted.call(feed, event);
185
+ const stateAfter = feed.currentState;
186
+ expect(stateAfter).toBe(stateBefore);
187
+ });
188
+ });
@@ -1,12 +1,12 @@
1
1
  import type { Feed } from '../../../feed';
2
2
  import type {
3
+ ActivityPinResponse,
3
4
  ActivityResponse,
4
5
  BookmarkDeletedEvent,
5
6
  BookmarkResponse,
6
7
  } from '../../../gen/models';
7
- import type { EventPayload, UpdateStateResult } from '../../../types-internal';
8
-
9
- import { updateActivityInState } from '../activity';
8
+ import type { EventPayload } from '../../../types-internal';
9
+ import { updateEntityInArray } from '../../../utils';
10
10
 
11
11
  // Helper function to check if two bookmarks are the same
12
12
  // A bookmark is identified by activity_id + folder_id + user_id
@@ -21,64 +21,102 @@ export const isSameBookmark = (
21
21
  );
22
22
  };
23
23
 
24
- export const removeBookmarkFromActivities = (
25
- event: BookmarkDeletedEvent,
26
- activities: ActivityResponse[] | undefined,
27
- isCurrentUser: boolean,
28
- ): UpdateStateResult<{ activities: ActivityResponse[] }> => {
29
- if (!activities) {
30
- return { changed: false, activities: [] };
31
- }
24
+ const sharedUpdateActivity = ({
25
+ currentActivity,
26
+ event,
27
+ eventBelongsToCurrentUser,
28
+ }: {
29
+ currentActivity: ActivityResponse;
30
+ event: BookmarkDeletedEvent;
31
+ eventBelongsToCurrentUser: boolean;
32
+ }): ActivityResponse => {
33
+ let newOwnBookmarks = currentActivity.own_bookmarks;
32
34
 
33
- const activityIndex = activities.findIndex(
34
- (a) => a.id === event.bookmark.activity.id,
35
- );
36
- if (activityIndex === -1) {
37
- return { changed: false, activities };
35
+ if (eventBelongsToCurrentUser) {
36
+ newOwnBookmarks = currentActivity.own_bookmarks.filter(
37
+ (bookmark) => !isSameBookmark(bookmark, event.bookmark),
38
+ );
38
39
  }
39
40
 
40
- const activity = activities[activityIndex];
41
- const updatedActivity = removeBookmarkFromActivity(
42
- event,
43
- activity,
44
- isCurrentUser,
45
- );
46
- return updateActivityInState(updatedActivity, activities, true);
41
+ return {
42
+ ...event.bookmark.activity,
43
+ own_bookmarks: newOwnBookmarks,
44
+ own_reactions: currentActivity.own_reactions,
45
+ };
47
46
  };
48
47
 
49
- export const removeBookmarkFromActivity = (
48
+ export const removeBookmarkFromActivities = (
50
49
  event: BookmarkDeletedEvent,
51
- activity: ActivityResponse,
52
- isCurrentUser: boolean,
53
- ): UpdateStateResult<ActivityResponse> => {
54
- // Update own_bookmarks if the bookmark is from the current user
55
- const ownBookmarks = isCurrentUser
56
- ? (activity.own_bookmarks || []).filter(
57
- (bookmark) => !isSameBookmark(bookmark, event.bookmark),
58
- )
59
- : activity.own_bookmarks;
50
+ activities: ActivityResponse[] | undefined,
51
+ eventBelongsToCurrentUser: boolean,
52
+ ) =>
53
+ updateEntityInArray({
54
+ entities: activities,
55
+ matcher: (activity) => activity.id === event.bookmark.activity.id,
56
+ updater: (matchedActivity) =>
57
+ sharedUpdateActivity({
58
+ currentActivity: matchedActivity,
59
+ event,
60
+ eventBelongsToCurrentUser,
61
+ }),
62
+ });
60
63
 
61
- return {
62
- ...activity,
63
- own_bookmarks: ownBookmarks,
64
- changed: true,
65
- };
66
- };
64
+ export const removeBookmarkFromPinnedActivities = (
65
+ event: BookmarkDeletedEvent,
66
+ pinnedActivities: ActivityPinResponse[] | undefined,
67
+ eventBelongsToCurrentUser: boolean,
68
+ ) =>
69
+ updateEntityInArray({
70
+ entities: pinnedActivities,
71
+ matcher: (pinnedActivity) =>
72
+ pinnedActivity.activity.id === event.bookmark.activity.id,
73
+ updater: (matchedPinnedActivity) => {
74
+ const newActivity = sharedUpdateActivity({
75
+ currentActivity: matchedPinnedActivity.activity,
76
+ event,
77
+ eventBelongsToCurrentUser,
78
+ });
79
+
80
+ if (newActivity === matchedPinnedActivity.activity) {
81
+ return matchedPinnedActivity;
82
+ }
83
+
84
+ return {
85
+ ...matchedPinnedActivity,
86
+ activity: newActivity,
87
+ };
88
+ },
89
+ });
67
90
 
68
91
  export function handleBookmarkDeleted(
69
92
  this: Feed,
70
93
  event: EventPayload<'feeds.bookmark.deleted'>,
71
94
  ) {
72
- const currentActivities = this.currentState.activities;
95
+ const {
96
+ activities: currentActivities,
97
+ pinned_activities: currentPinnedActivities,
98
+ } = this.currentState;
73
99
  const { connected_user: connectedUser } = this.client.state.getLatestValue();
74
- const isCurrentUser = event.bookmark.user.id === connectedUser?.id;
100
+ const eventBelongsToCurrentUser =
101
+ event.bookmark.user.id === connectedUser?.id;
75
102
 
76
- const result = removeBookmarkFromActivities(
77
- event,
78
- currentActivities,
79
- isCurrentUser,
80
- );
81
- if (result.changed) {
82
- this.state.partialNext({ activities: result.activities });
103
+ const [result1, result2] = [
104
+ removeBookmarkFromActivities(
105
+ event,
106
+ currentActivities,
107
+ eventBelongsToCurrentUser,
108
+ ),
109
+ removeBookmarkFromPinnedActivities(
110
+ event,
111
+ currentPinnedActivities,
112
+ eventBelongsToCurrentUser,
113
+ ),
114
+ ];
115
+
116
+ if (result1.changed || result2.changed) {
117
+ this.state.partialNext({
118
+ activities: result1.entities,
119
+ pinned_activities: result2.entities,
120
+ });
83
121
  }
84
122
  }