@amityco/ts-sdk 0.0.1-beta.41.1 → 0.0.1-beta.42.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 (122) hide show
  1. package/dist/@types/core/model.d.ts +3 -1
  2. package/dist/@types/core/model.d.ts.map +1 -1
  3. package/dist/@types/domains/category.d.ts +1 -1
  4. package/dist/@types/domains/category.d.ts.map +1 -1
  5. package/dist/@types/domains/comment.d.ts +1 -1
  6. package/dist/@types/domains/comment.d.ts.map +1 -1
  7. package/dist/@types/domains/community.d.ts +1 -1
  8. package/dist/@types/domains/community.d.ts.map +1 -1
  9. package/dist/@types/domains/message.d.ts +1 -1
  10. package/dist/@types/domains/message.d.ts.map +1 -1
  11. package/dist/@types/domains/post.d.ts +1 -1
  12. package/dist/@types/domains/post.d.ts.map +1 -1
  13. package/dist/@types/domains/subChannel.d.ts +1 -1
  14. package/dist/@types/domains/subChannel.d.ts.map +1 -1
  15. package/dist/cache/utils/index.d.ts +1 -0
  16. package/dist/cache/utils/index.d.ts.map +1 -1
  17. package/dist/category/api/queryCategories.d.ts.map +1 -1
  18. package/dist/category/observers/liveCategories.d.ts.map +1 -1
  19. package/dist/channel/observers/liveChannel.d.ts.map +1 -1
  20. package/dist/channel/observers/liveChannels.d.ts.map +1 -1
  21. package/dist/comment/api/queryComments.d.ts.map +1 -1
  22. package/dist/comment/api/tests/queryComments.test.d.ts +2 -0
  23. package/dist/comment/api/tests/queryComments.test.d.ts.map +1 -0
  24. package/dist/comment/observers/liveComments.d.ts.map +1 -1
  25. package/dist/community/api/queryCommunities.d.ts.map +1 -1
  26. package/dist/community/observers/liveCommunities.d.ts.map +1 -1
  27. package/dist/core/model/idResolvers.d.ts.map +1 -1
  28. package/dist/core/model/identifyModel.d.ts.map +1 -1
  29. package/dist/core/model/index.d.ts +1 -1
  30. package/dist/core/model/index.d.ts.map +1 -1
  31. package/dist/follow/api/getFollowInfo.d.ts.map +1 -1
  32. package/dist/follow/observers/index.d.ts +1 -0
  33. package/dist/follow/observers/index.d.ts.map +1 -1
  34. package/dist/follow/observers/liveFollowInfo.d.ts +21 -0
  35. package/dist/follow/observers/liveFollowInfo.d.ts.map +1 -0
  36. package/dist/follow/observers/tests/liveFollowInfo.test.d.ts +2 -0
  37. package/dist/follow/observers/tests/liveFollowInfo.test.d.ts.map +1 -0
  38. package/dist/index.cjs.js +112 -63
  39. package/dist/index.esm.js +112 -64
  40. package/dist/index.umd.js +4 -4
  41. package/dist/message/api/createMessage.d.ts +2 -1
  42. package/dist/message/api/createMessage.d.ts.map +1 -1
  43. package/dist/message/observers/liveMessages.d.ts.map +1 -1
  44. package/dist/message/utils/index.d.ts +3 -2
  45. package/dist/message/utils/index.d.ts.map +1 -1
  46. package/dist/post/api/queryPosts.d.ts.map +1 -1
  47. package/dist/post/observers/livePosts.d.ts.map +1 -1
  48. package/dist/subChannel/api/querySubChannels.d.ts.map +1 -1
  49. package/dist/subChannel/observers/liveSubChannels.d.ts.map +1 -1
  50. package/dist/utils/constants.d.ts +1 -0
  51. package/dist/utils/constants.d.ts.map +1 -1
  52. package/dist/utils/liveObject.d.ts +1 -1
  53. package/dist/utils/liveObject.d.ts.map +1 -1
  54. package/dist/utils/tests/dummy/comment.d.ts +77 -0
  55. package/dist/utils/tests/dummy/comment.d.ts.map +1 -0
  56. package/dist/utils/tests/dummy/community.d.ts +1 -1
  57. package/dist/utils/tests/dummy/index.d.ts +1 -0
  58. package/dist/utils/tests/dummy/index.d.ts.map +1 -1
  59. package/dist/utils/tests/dummy/post.d.ts +0 -12
  60. package/dist/utils/tests/dummy/post.d.ts.map +1 -1
  61. package/package.json +1 -1
  62. package/src/@types/core/model.ts +3 -1
  63. package/src/@types/domains/category.ts +1 -1
  64. package/src/@types/domains/comment.ts +1 -1
  65. package/src/@types/domains/community.ts +1 -1
  66. package/src/@types/domains/message.ts +1 -1
  67. package/src/@types/domains/post.ts +1 -1
  68. package/src/@types/domains/subChannel.ts +1 -1
  69. package/src/cache/utils/index.ts +5 -2
  70. package/src/category/api/queryCategories.ts +2 -1
  71. package/src/category/observers/liveCategories.ts +3 -1
  72. package/src/category/observers/tests/liveCategories.test.ts +1 -3
  73. package/src/channel/api/tests/addChannelMembers.test.ts +13 -0
  74. package/src/channel/api/tests/queryChannelMembers.test.ts +1 -0
  75. package/src/channel/api/tests/removeChannelMembers.test.ts +9 -2
  76. package/src/channel/observers/liveChannel.ts +16 -1
  77. package/src/channel/observers/liveChannels.ts +11 -9
  78. package/src/comment/api/queryComments.ts +2 -1
  79. package/src/comment/api/tests/createComment.test.ts +17 -112
  80. package/src/comment/api/tests/queryComments.test.ts +147 -0
  81. package/src/comment/api/tests/updateComment.test.ts +14 -85
  82. package/src/comment/observers/liveComments.ts +12 -7
  83. package/src/community/api/queryCommunities.ts +2 -1
  84. package/src/community/api/test/queryCommunity.test.ts +30 -0
  85. package/src/community/observers/liveCommunities.ts +13 -7
  86. package/src/community/observers/tests/liveCommunities.test.ts +2 -2
  87. package/src/core/model/idResolvers.ts +1 -0
  88. package/src/core/model/identifyModel.ts +2 -1
  89. package/src/core/model/index.ts +11 -2
  90. package/src/follow/api/getFollowInfo.ts +4 -8
  91. package/src/follow/observers/index.ts +1 -0
  92. package/src/follow/observers/liveFollowInfo.ts +29 -0
  93. package/src/follow/observers/tests/liveFollowInfo.test.ts +198 -0
  94. package/src/message/api/createMessage.ts +17 -5
  95. package/src/message/api/tests/createMessage.test.ts +36 -5
  96. package/src/message/api/tests/deleteMessage.test.ts +10 -3
  97. package/src/message/api/tests/getMessage.test.ts +9 -3
  98. package/src/message/api/tests/queryMessages.test.ts +30 -0
  99. package/src/message/api/tests/updateMessage.test.ts +9 -2
  100. package/src/message/observers/liveMessages.ts +15 -15
  101. package/src/message/tests/observers/liveMessages.test.ts +2 -2
  102. package/src/message/utils/index.ts +10 -1
  103. package/src/post/api/queryPosts.ts +2 -1
  104. package/src/post/api/tests/queryPosts.test.ts +30 -0
  105. package/src/post/observers/livePosts.ts +16 -11
  106. package/src/post/observers/tests/livePosts.test.ts +4 -5
  107. package/src/reaction/api/tests/addReaction.test.ts +13 -0
  108. package/src/reaction/api/tests/removeReaction.test.ts +13 -0
  109. package/src/subChannel/api/querySubChannels.ts +2 -1
  110. package/src/subChannel/observers/liveSubChannels.ts +6 -5
  111. package/src/subChannel/observers/tests/liveSubChannels.test.ts +5 -2
  112. package/src/utils/constants.ts +2 -1
  113. package/src/utils/liveObject.ts +1 -4
  114. package/src/utils/tests/dummy/comment.ts +268 -0
  115. package/src/utils/tests/dummy/index.ts +1 -0
  116. package/src/utils/tests/dummy/post.ts +0 -114
  117. package/dist/post/tests/api/getPost.test.d.ts +0 -2
  118. package/dist/post/tests/api/getPost.test.d.ts.map +0 -1
  119. package/dist/post/tests/api/queryPosts.test.d.ts +0 -2
  120. package/dist/post/tests/api/queryPosts.test.d.ts.map +0 -1
  121. package/src/post/tests/api/getPost.test.ts +0 -98
  122. package/src/post/tests/api/queryPosts.test.ts +0 -123
@@ -33,8 +33,17 @@ export const PAYLOAD2MODEL: Record<string, Amity.Domain> = {
33
33
  };
34
34
 
35
35
  /** hidden */
36
- export const hasUpdates = (prevData: Amity.UpdatedAt, nextData: Amity.UpdatedAt): boolean => {
37
- return new Date(prevData.updatedAt!) < new Date(nextData.updatedAt!);
36
+ export const hasUpdates = <T extends Amity.Model>(prevData: T, nextData: T): boolean => {
37
+ if ('updatedAt' in prevData && 'updatedAt' in nextData) {
38
+ return new Date(prevData.updatedAt!) < new Date(nextData.updatedAt!);
39
+ }
40
+
41
+ /*
42
+ * hasUpdates is used to identify the latest object among many. If the object
43
+ * does not extend ${Amity.UpdatedAt}, then there is no way to differentiate.
44
+ * So it is considered by default that the object hasUpdates
45
+ */
46
+ return true;
38
47
  };
39
48
 
40
49
  /** hidden */
@@ -1,12 +1,6 @@
1
1
  import { getActiveClient } from '~/client/api';
2
2
  import { pullFromCache, pushToCache } from '~/cache/api';
3
3
 
4
- const getCacheKey = (userId: Amity.User['userId']) => [
5
- 'followInfo',
6
- 'get',
7
- `${getActiveClient().userId}#${userId}`,
8
- ];
9
-
10
4
  /* begin_public_function
11
5
  id: user.relationship.get_follow_info, user.relationship.get_my_follow_info
12
6
  */
@@ -44,7 +38,9 @@ export const getFollowInfo = async (
44
38
  : data.followCounts[0];
45
39
 
46
40
  if (client.cache) {
47
- pushToCache(getCacheKey(userId), followInfo, { cachedAt });
41
+ pushToCache(['followInfo', 'get', userId], followInfo, {
42
+ cachedAt,
43
+ });
48
44
  }
49
45
 
50
46
  return {
@@ -77,7 +73,7 @@ getFollowInfo.locally = (
77
73
  return;
78
74
  }
79
75
 
80
- const cached = pullFromCache<Amity.FollowInfo>(getCacheKey(userId));
76
+ const cached = pullFromCache<Amity.FollowInfo>(['followInfo', 'get', userId]);
81
77
 
82
78
  if (!cached) {
83
79
  return;
@@ -4,3 +4,4 @@ export * from './observeFollowings';
4
4
 
5
5
  export * from './liveFollowers';
6
6
  export * from './liveFollowings';
7
+ export * from './liveFollowInfo';
@@ -0,0 +1,29 @@
1
+ import { liveObject } from '~/utils/liveObject';
2
+ import { getFollowInfo } from '../api';
3
+ import { onFollowInfoUpdated } from '../events';
4
+
5
+ /**
6
+ * ```js
7
+ * import { liveFollowInfo } from '@amityco/ts-sdk';
8
+ *
9
+ * let followInfo;
10
+ *
11
+ * const unsubscribe = liveFollowInfo(userId, response => {
12
+ * followInfo = response.data;
13
+ * });
14
+ * ```
15
+ *
16
+ * Observe all mutation on a given {@link Amity.FollowCount} object
17
+ *
18
+ * @param userId the ID of the current user
19
+ * @param callback the function to call when new data are available
20
+ * @returns An {@link Amity.Unsubscriber} function to run when willing to stop observing
21
+ *
22
+ * @category FollowInfo Live Object
23
+ */
24
+ export const liveFollowInfo = (
25
+ userId: Amity.FollowInfo['userId'],
26
+ callback: Amity.LiveObjectCallback<Amity.FollowInfo>,
27
+ ): Amity.Unsubscriber => {
28
+ return liveObject(userId, callback, 'userId', getFollowInfo, [onFollowInfoUpdated]);
29
+ };
@@ -0,0 +1,198 @@
1
+ import { disableCache, enableCache } from '~/cache/api';
2
+ import {
3
+ client,
4
+ connectClient,
5
+ disconnectClient,
6
+ follow21,
7
+ follow11,
8
+ user11,
9
+ pause,
10
+ date,
11
+ } from '~/utils/tests';
12
+
13
+ import { liveFollowInfo } from '../liveFollowInfo';
14
+
15
+ const FOLLOWING_COUNT = 2;
16
+ const getFollowInfoMePayload = ({
17
+ userId = user11.userId,
18
+ followerCount = 0,
19
+ pendingCount = 0,
20
+ followingCount = FOLLOWING_COUNT,
21
+ } = {}): Amity.FollowInfoMePayload => {
22
+ return {
23
+ followCounts: [
24
+ {
25
+ userId,
26
+ followerCount,
27
+ pendingCount,
28
+ followingCount,
29
+ },
30
+ ],
31
+ };
32
+ };
33
+
34
+ const getFollowMeStatusPayload = (
35
+ status: Amity.FollowStatus['status'] = follow11.status,
36
+ ): Amity.FollowStatusPayload => {
37
+ return {
38
+ follows: [
39
+ {
40
+ from: follow11.from,
41
+ to: follow11.to,
42
+ status,
43
+ createdAt: date,
44
+ updatedAt: date,
45
+ },
46
+ ],
47
+ };
48
+ };
49
+
50
+ const getFollowStatusPayload = (
51
+ status: Amity.FollowStatus['status'] = follow21.status,
52
+ ): Amity.FollowStatusPayload => {
53
+ return {
54
+ follows: [
55
+ {
56
+ from: follow21.from,
57
+ to: follow21.to,
58
+ status,
59
+ createdAt: date,
60
+ updatedAt: date,
61
+ },
62
+ ],
63
+ };
64
+ };
65
+
66
+ describe('liveFollowInfo > me', () => {
67
+ beforeAll(connectClient);
68
+ afterAll(disconnectClient);
69
+
70
+ beforeEach(enableCache);
71
+ afterEach(disableCache);
72
+
73
+ const tests: [
74
+ string,
75
+ keyof Amity.MqttFollowEvents,
76
+ Amity.FollowStatusPayload,
77
+ Amity.FollowInfoMePayload,
78
+ ][] = [
79
+ [
80
+ 'it should update follow info collection on accepted',
81
+ 'follow.accepted',
82
+ getFollowMeStatusPayload('accepted'),
83
+ getFollowInfoMePayload({ userId: user11.userId, followingCount: FOLLOWING_COUNT + 1 }),
84
+ ],
85
+ [
86
+ 'it should update follow info collection on unfollowed',
87
+ 'follow.unfollowed',
88
+ getFollowMeStatusPayload('none'),
89
+ getFollowInfoMePayload({ userId: user11.userId, followingCount: FOLLOWING_COUNT - 1 }),
90
+ ],
91
+ [
92
+ 'it should update follow info collection on requestDeclined',
93
+ 'follow.requestDeclined',
94
+ getFollowMeStatusPayload('none'),
95
+ getFollowInfoMePayload({ userId: user11.userId, followingCount: FOLLOWING_COUNT }),
96
+ ],
97
+ [
98
+ 'it should update follow info collection on followerDeleted',
99
+ 'follow.followerDeleted',
100
+ getFollowMeStatusPayload('none'),
101
+ getFollowInfoMePayload({ userId: user11.userId, followingCount: FOLLOWING_COUNT - 1 }),
102
+ ],
103
+ ];
104
+
105
+ test.each(tests)('%s', async (description, event, payload, payloadFrom) => {
106
+ const callback = jest.fn();
107
+ client.http.get = jest
108
+ .fn()
109
+ .mockResolvedValue({ data: getFollowInfoMePayload() })
110
+ .mockResolvedValueOnce({ data: payloadFrom })
111
+ .mockResolvedValueOnce({ data: payloadFrom });
112
+
113
+ const unsub = liveFollowInfo(user11.userId, callback);
114
+ await pause();
115
+
116
+ client.emitter.emit(event, payload);
117
+ await pause();
118
+
119
+ expect(callback).toHaveBeenCalled();
120
+
121
+ expect(callback).toHaveBeenNthCalledWith(
122
+ 3,
123
+ expect.objectContaining({
124
+ data: payloadFrom.followCounts[0],
125
+ loading: false,
126
+ }),
127
+ );
128
+
129
+ unsub();
130
+ });
131
+ });
132
+
133
+ describe('liveFollowInfo > other', () => {
134
+ beforeAll(connectClient);
135
+ afterAll(disconnectClient);
136
+
137
+ beforeEach(enableCache);
138
+ afterEach(disableCache);
139
+
140
+ const tests: [
141
+ string,
142
+ keyof Amity.MqttFollowEvents,
143
+ Amity.FollowStatusPayload,
144
+ Amity.FollowInfoMePayload,
145
+ ][] = [
146
+ [
147
+ 'it should update follow info collection on accepted',
148
+ 'follow.accepted',
149
+ getFollowStatusPayload('accepted'),
150
+ getFollowInfoMePayload({ userId: follow21.from, followingCount: FOLLOWING_COUNT + 1 }),
151
+ ],
152
+ [
153
+ 'it should update follow info collection on unfollowed',
154
+ 'follow.unfollowed',
155
+ getFollowStatusPayload('none'),
156
+ getFollowInfoMePayload({ userId: follow21.from, followingCount: FOLLOWING_COUNT - 1 }),
157
+ ],
158
+ [
159
+ 'it should update follow info collection on requestDeclined',
160
+ 'follow.requestDeclined',
161
+ getFollowStatusPayload('none'),
162
+ getFollowInfoMePayload({ userId: follow21.from, followingCount: FOLLOWING_COUNT }),
163
+ ],
164
+ [
165
+ 'it should update follow info collection on followerDeleted',
166
+ 'follow.followerDeleted',
167
+ getFollowStatusPayload('none'),
168
+ getFollowInfoMePayload({ userId: follow21.from, followingCount: FOLLOWING_COUNT - 1 }),
169
+ ],
170
+ ];
171
+
172
+ test.each(tests)('%s', async (description, event, payload, payloadFrom) => {
173
+ const callback = jest.fn();
174
+ client.http.get = jest
175
+ .fn()
176
+ .mockResolvedValue({ data: getFollowInfoMePayload({ userId: follow21.from }) })
177
+ .mockResolvedValueOnce({ data: payloadFrom })
178
+ .mockResolvedValueOnce({ data: payloadFrom });
179
+
180
+ const unsub = liveFollowInfo(follow21.from, callback);
181
+ await pause();
182
+
183
+ client.emitter.emit(event, payload);
184
+ await pause();
185
+
186
+ expect(callback).toHaveBeenCalled();
187
+
188
+ expect(callback).toHaveBeenNthCalledWith(
189
+ 3,
190
+ expect.objectContaining({
191
+ data: payloadFrom.followCounts[0],
192
+ loading: false,
193
+ }),
194
+ );
195
+
196
+ unsub();
197
+ });
198
+ });
@@ -44,12 +44,23 @@ export const createMessage = async <T extends Amity.MessageContentType>(
44
44
  > & {
45
45
  data?: Amity.Message<T>['data'];
46
46
  fileId?: Amity.File['fileId'];
47
+ // Custom referenceId is a workaround for the case:
48
+ // User wants to create an image message. He should
49
+ // - create and show dummy message in ui
50
+ // - upload image to get fileId, while uploading show a spinner in dummy message in ui
51
+ // - call createMessage and pass fileId
52
+ // - replace dummy message with the actual message received from backend
53
+ // Custom referenceId is needed to make relation between dummy message and actual message.
54
+ // TODO: passing file src into createMessage so that sdk uploads files itself,
55
+ // returns the dummy message bound with the file to eliminate the need to create
56
+ // the dummy message on the user side,
57
+ referenceId?: string;
47
58
  },
48
59
  ): Promise<Amity.Cached<Amity.Message>> => {
49
60
  const client = getActiveClient();
50
61
  client.log('message/createMessage', bundle);
51
62
 
52
- const referenceId = uniqueId || getLocalId();
63
+ const referenceId = bundle.referenceId || uniqueId || getLocalId();
53
64
  uniqueId = undefined;
54
65
 
55
66
  const { data: payload } = await client.http.post<Amity.MessagePayload>('/api/v5/messages', {
@@ -91,9 +102,10 @@ export const createMessage = async <T extends Amity.MessageContentType>(
91
102
  *
92
103
  * @category Message API
93
104
  */
94
- createMessage.optimistically = (
95
- bundle: Parameters<typeof createMessage>[0],
96
- ): Amity.Cached<Amity.Message> | undefined => {
105
+ createMessage.optimistically = ({
106
+ referenceId,
107
+ ...bundle
108
+ }: Parameters<typeof createMessage>[0]): Amity.Cached<Amity.Message> | undefined => {
97
109
  const client = getActiveClient();
98
110
 
99
111
  if (!client.cache) return;
@@ -105,7 +117,7 @@ createMessage.optimistically = (
105
117
  * Updated to handle client requirement to add messageId while uploading
106
118
  * message with image. Temporary!
107
119
  */
108
- uniqueId = getLocalId();
120
+ uniqueId = referenceId || getLocalId();
109
121
  const bundleWithMessageId = { messageId: uniqueId, uniqueId, ...bundle };
110
122
 
111
123
  client.log('message/createMessage.optimistically', bundleWithMessageId);
@@ -1,3 +1,4 @@
1
+ import { ASCApiError } from '~/core/errors';
1
2
  import { UNSYNCED_OBJECT_CACHED_AT_VALUE } from '~/utils/constants';
2
3
  import { disableCache, enableCache, pullFromCache, pushToCache } from '~/cache/api';
3
4
 
@@ -28,6 +29,7 @@ describe('createMessage', () => {
28
29
 
29
30
  afterEach(disableCache);
30
31
 
32
+ // integration_test_id: 9691969d-6ba8-4af6-93d6-dfe9134e9796
31
33
  test('should return created message', async () => {
32
34
  client.http.post = jest.fn().mockResolvedValue(response);
33
35
 
@@ -37,10 +39,15 @@ describe('createMessage', () => {
37
39
  expect(created.uniqueId).toBeDefined();
38
40
  });
39
41
 
40
- test('should throw an error if request fails', async () => {
41
- client.http.post = jest.fn().mockRejectedValueOnce(new Error('error'));
42
+ // integration_test_id: acc3be84-9234-4af1-88a1-cbb517fa247f
43
+ test('should throw an 404300 if user has no permission', async () => {
44
+ client.http.post = jest
45
+ .fn()
46
+ .mockRejectedValueOnce(
47
+ new ASCApiError('forbidden', Amity.ServerError.FORBIDDEN, Amity.ErrorLevel.ERROR),
48
+ );
42
49
 
43
- await expect(createMessage(messageWithoutId)).rejects.toThrow('error');
50
+ await expect(createMessage(messageWithoutId)).rejects.toThrow('400300');
44
51
  });
45
52
 
46
53
  test('should put message into cache', async () => {
@@ -141,9 +148,9 @@ describe('uniqueId / referenceId', () => {
141
148
  subChannelId: messageToCreate.subChannelId,
142
149
  };
143
150
 
144
- beforeAll(enableCache);
151
+ beforeEach(enableCache);
145
152
 
146
- afterAll(disableCache);
153
+ afterEach(disableCache);
147
154
 
148
155
  it('should optimistically create message with uniqueId', () => {
149
156
  const { data: message } = createMessage.optimistically(bundle)!;
@@ -208,4 +215,28 @@ describe('uniqueId / referenceId', () => {
208
215
  }),
209
216
  );
210
217
  });
218
+
219
+ describe('custom referenceId', () => {
220
+ it('should handle custom referenceId for optimistic message', () => {
221
+ const referenceId = 'custom-reference-id';
222
+
223
+ const { data: message } = createMessage.optimistically({ ...bundle, referenceId })!;
224
+
225
+ expect(message.uniqueId).toEqual(referenceId);
226
+ });
227
+
228
+ it('should handle custom referenceId if cache is turned off', async () => {
229
+ disableCache();
230
+ const referenceId = 'custom-reference-id';
231
+ client.http.post = jest.fn().mockResolvedValue({ data: { messages: [rawMessageToCreate] } });
232
+
233
+ runQuery(createQuery(createMessage, { ...bundle, referenceId }));
234
+ await pause();
235
+
236
+ expect(client.http.post).toHaveBeenCalledWith(
237
+ expect.anything(),
238
+ expect.objectContaining({ referenceId }),
239
+ );
240
+ });
241
+ });
211
242
  });
@@ -1,3 +1,4 @@
1
+ import { ASCApiError } from '~/core/errors';
1
2
  import { disableCache, enableCache, pullFromCache, pushToCache } from '~/cache/api';
2
3
  import { client, generateRawMessage } from '~/utils/tests';
3
4
 
@@ -20,6 +21,7 @@ const getResolvedMessageValue = () => ({
20
21
  });
21
22
 
22
23
  describe('deleteMessage', () => {
24
+ // integration_test_id: 16106137-0e4a-4d2f-b8cf-1a6c571bf0c7
23
25
  test('should return deleted message', async () => {
24
26
  client.http.delete = jest.fn();
25
27
  client.http.get = jest.fn().mockResolvedValueOnce(getResolvedMessageValue());
@@ -27,10 +29,15 @@ describe('deleteMessage', () => {
27
29
  await expect(deleteMessage(deleteId)).resolves.toEqual(deletedMessage);
28
30
  });
29
31
 
30
- test('should throw an error if request fails', async () => {
31
- client.http.delete = jest.fn().mockRejectedValueOnce(new Error('error'));
32
+ // integration_test_id: 27d393d9-480e-4eec-bfdb-f04738801b04
33
+ test('should return 400400 if message not found', async () => {
34
+ client.http.delete = jest
35
+ .fn()
36
+ .mockRejectedValueOnce(
37
+ new ASCApiError('not found', Amity.ServerError.ITEM_NOT_FOUND, Amity.ErrorLevel.ERROR),
38
+ );
32
39
 
33
- await expect(deleteMessage(deleteId)).rejects.toThrow('error');
40
+ await expect(deleteMessage(deleteId)).rejects.toThrow('400400');
34
41
  });
35
42
 
36
43
  test('should update cache after deleted messages', async () => {
@@ -21,6 +21,7 @@ const getResolvedMessageValue = () => ({
21
21
  });
22
22
 
23
23
  describe('getMessage', () => {
24
+ // integration_test_id: bc048747-17ea-43fe-9f97-7b2bf008c476
24
25
  test('should return fetched message', async () => {
25
26
  client.http.get = jest.fn().mockResolvedValueOnce(getResolvedMessageValue());
26
27
 
@@ -41,10 +42,15 @@ describe('getMessage', () => {
41
42
  disableCache();
42
43
  });
43
44
 
44
- test('should throw an error if request fails', async () => {
45
- client.http.get = jest.fn().mockRejectedValueOnce(new Error('error'));
45
+ // integration_test_id: 81375153-2886-46e0-a164-7f3902e77b55
46
+ test('should return 400300 if unauthorized', async () => {
47
+ client.http.get = jest
48
+ .fn()
49
+ .mockRejectedValueOnce(
50
+ new ASCApiError('forbidden', Amity.ServerError.FORBIDDEN, Amity.ErrorLevel.ERROR),
51
+ );
46
52
 
47
- await expect(getMessage(messageId)).rejects.toThrow('error');
53
+ await expect(getMessage(messageId)).rejects.toThrow('400300');
48
54
  });
49
55
 
50
56
  test('should fire event `onMessageFetched`', async () => {
@@ -74,6 +74,36 @@ describe('queryMessages', () => {
74
74
  await queryMessages(queryCriteria);
75
75
  await expect(callbackPromise).resolves.toEqual(messagesToQuery[0]);
76
76
  });
77
+
78
+ test('it should set isDeleted to undefined if includeDeleted is true', async () => {
79
+ const mockFn = jest.fn();
80
+ client.http.get = mockFn.mockResolvedValue(getResolvedMessageValue());
81
+
82
+ await queryMessages({ ...queryCriteriaWithoutPage, includeDeleted: true });
83
+
84
+ expect(mockFn.mock.lastCall).toHaveLength(2);
85
+ expect(mockFn.mock.lastCall[1].params.isDeleted).toBeUndefined();
86
+ });
87
+
88
+ test('it should set isDeleted to false if includeDeleted is false', async () => {
89
+ const mockFn = jest.fn();
90
+ client.http.get = mockFn.mockResolvedValue(getResolvedMessageValue());
91
+
92
+ await queryMessages({ ...queryCriteriaWithoutPage, includeDeleted: false });
93
+
94
+ expect(mockFn.mock.lastCall).toHaveLength(2);
95
+ expect(mockFn.mock.lastCall[1].params.isDeleted).toBe(false);
96
+ });
97
+
98
+ test('it should set isDeleted to undefined if includeDeleted is undefined', async () => {
99
+ const mockFn = jest.fn();
100
+ client.http.get = mockFn.mockResolvedValue(getResolvedMessageValue());
101
+
102
+ await queryMessages(queryCriteriaWithoutPage);
103
+
104
+ expect(mockFn.mock.lastCall).toHaveLength(2);
105
+ expect(mockFn.mock.lastCall[1].params.isDeleted).toBeUndefined();
106
+ });
77
107
  });
78
108
 
79
109
  describe('queryMessages.locally', () => {
@@ -1,3 +1,4 @@
1
+ import { ASCApiError } from '~/core/errors';
1
2
  import { disableCache, enableCache, pullFromCache, pushToCache } from '~/cache/api';
2
3
  import { client, generateRawMessage } from '~/utils/tests';
3
4
 
@@ -21,6 +22,7 @@ const getResolvedMessageValue = () => ({
21
22
  });
22
23
 
23
24
  describe('updateMessage', () => {
25
+ // integration_test_id: 3ae17ea2-dee5-4418-88af-fe809ee48a52
24
26
  test('should return updated message', async () => {
25
27
  client.http.put = jest.fn().mockResolvedValueOnce(getResolvedMessageValue());
26
28
 
@@ -29,10 +31,15 @@ describe('updateMessage', () => {
29
31
  await expect(recieved).resolves.toEqual(expect.objectContaining({ data: updatedMessage }));
30
32
  });
31
33
 
34
+ // integration_test_id: 3ae17ea2-dee5-4418-88af-fe809ee48a52
32
35
  test('should throw an error if request fails', async () => {
33
- client.http.put = jest.fn().mockRejectedValueOnce(new Error('error'));
36
+ client.http.put = jest
37
+ .fn()
38
+ .mockRejectedValueOnce(
39
+ new ASCApiError('not found', Amity.ServerError.ITEM_NOT_FOUND, Amity.ErrorLevel.ERROR),
40
+ );
34
41
 
35
- await expect(updateMessage(messageId, patch)).rejects.toThrow('error');
42
+ await expect(updateMessage(messageId, patch)).rejects.toThrow('400400');
36
43
  });
37
44
 
38
45
  test('should update cache after updated messages', async () => {
@@ -1,8 +1,8 @@
1
1
  /* eslint-disable no-use-before-define */
2
2
  import { getResolver } from '~/core/model';
3
3
  import { getActiveClient } from '~/client/api';
4
- import { pushToCache, pullFromCache } from '~/cache/api';
5
- import { createQuery, runQuery, queryOptions, filterByPropEquality } from '~/core/query';
4
+ import { dropFromCache, pullFromCache, pushToCache } from '~/cache/api';
5
+ import { createQuery, filterByPropEquality, queryOptions, runQuery } from '~/core/query';
6
6
 
7
7
  import {
8
8
  COLLECTION_DEFAULT_CACHING_POLICY,
@@ -10,15 +10,16 @@ import {
10
10
  ENABLE_CACHE_MESSAGE,
11
11
  } from '~/utils/constants';
12
12
 
13
+ import { CACHE_SHORTEN_LIFESPAN } from '~/cache/utils';
13
14
  import {
14
15
  onMessageCreated,
15
16
  onMessageDeleted,
16
- onMessageUpdated,
17
- onMessageFlagged,
18
- onMessageUnflagged,
19
17
  onMessageFlagCleared,
18
+ onMessageFlagged,
20
19
  onMessageReactionAdded,
21
20
  onMessageReactionRemoved,
21
+ onMessageUnflagged,
22
+ onMessageUpdated,
22
23
  } from '../events';
23
24
  import { onMessageFetched } from '../events/onMessageFetched';
24
25
  import { queryMessages } from '../api';
@@ -73,9 +74,11 @@ export const liveMessages = (
73
74
 
74
75
  /*
75
76
  * for cases when message is deleted via RTE, this flag is used to get
76
- * items fro cache that are deleted | !deleted
77
+ * items from cache that are !deleted
77
78
  */
78
- messages = filterByPropEquality(messages, 'isDeleted', params.isDeleted);
79
+ if (params.includeDeleted === false) {
80
+ messages = filterByPropEquality(messages, 'isDeleted', false);
81
+ }
79
82
 
80
83
  callback({
81
84
  onNextPage: onFetch,
@@ -86,16 +89,12 @@ export const liveMessages = (
86
89
  });
87
90
  };
88
91
 
89
- const realtimeRouter = (action: Amity.MessageActionType) => (message: Amity.Message) => {
92
+ const realtimeRouter = (_: Amity.MessageActionType) => (message: Amity.Message) => {
90
93
  const collection = pullFromCache<Amity.MessageLiveCollectionCache>(cacheKey)?.data;
91
94
 
92
- if (params.subChannelId !== message.subChannelId || !collection) return;
95
+ if (params.subChannelId !== message?.subChannelId || !collection) return;
93
96
 
94
- if (action === 'onCreate') {
95
- collection.data = [...new Set([message.messageId, ...collection.data])];
96
- } else if (['onDelete'].includes(action)) {
97
- collection.data = collection.data.filter(m => m !== message.messageId);
98
- }
97
+ collection.data = [...new Set([message.messageId, ...collection.data])];
99
98
 
100
99
  pushToCache(cacheKey, collection);
101
100
 
@@ -132,7 +131,7 @@ export const liveMessages = (
132
131
 
133
132
  responder(data);
134
133
  },
135
- queryOptions(policy),
134
+ queryOptions(policy, CACHE_SHORTEN_LIFESPAN),
136
135
  );
137
136
  };
138
137
 
@@ -153,5 +152,6 @@ export const liveMessages = (
153
152
  return () => {
154
153
  log(`liveMessages(tmpid: ${timestamp}) > dispose`);
155
154
  disposers.forEach(fn => fn());
155
+ dropFromCache(cacheKey);
156
156
  };
157
157
  };
@@ -146,7 +146,7 @@ describe('liveMessages', () => {
146
146
  },
147
147
  });
148
148
 
149
- liveMessages({ subChannelId, isDeleted: false }, callback);
149
+ liveMessages({ subChannelId, includeDeleted: false }, callback);
150
150
 
151
151
  expect(callback).toHaveBeenCalledTimes(1);
152
152
 
@@ -351,7 +351,7 @@ describe('liveMessages', () => {
351
351
  expect(callback).toHaveBeenNthCalledWith(
352
352
  3,
353
353
  expect.objectContaining({
354
- data: [],
354
+ data: [message],
355
355
  error: undefined,
356
356
  loading: false,
357
357
  }),
@@ -89,8 +89,15 @@ export const convertResponseFromRaw = <T extends Amity.MessagePayload>(
89
89
  };
90
90
  };
91
91
 
92
- type RawQueryMessages = Omit<Amity.QueryMessages, 'page' | 'sortBy' | 'subChannelId' | 'tags'> & {
92
+ type RawQueryMessages = Omit<
93
+ Amity.QueryMessages,
94
+ /*
95
+ * include deleted is for internal sdk use only. The server expects isDeleted
96
+ */
97
+ 'page' | 'sortBy' | 'subChannelId' | 'tags' | 'includeDeleted'
98
+ > & {
93
99
  includeTags?: Amity.QueryMessages['tags'];
100
+ isDeleted?: Amity.Message['isDeleted'];
94
101
  messageFeedId: Amity.QueryMessages['subChannelId'];
95
102
  options: {
96
103
  sortBy?: Amity.QueryMessages['sortBy'];
@@ -111,11 +118,13 @@ export function convertQueryParams({
111
118
  sortBy,
112
119
  subChannelId,
113
120
  tags,
121
+ includeDeleted,
114
122
  ...rest
115
123
  }: Amity.QueryMessages): RawQueryMessages {
116
124
  const out: RawQueryMessages = {
117
125
  ...rest,
118
126
  messageFeedId: subChannelId,
127
+ isDeleted: includeDeleted === false ? false : undefined,
119
128
  options: { token: toToken(page || { limit: 10 }, 'afterbeforeraw')! },
120
129
  };
121
130
 
@@ -30,7 +30,7 @@ export const queryPosts = async (
30
30
  const client = getActiveClient();
31
31
  client.log('post/queryPosts', query);
32
32
 
33
- const { page = { limit: 10 }, ...params } = query;
33
+ const { page = { limit: 10 }, includeDeleted, ...params } = query;
34
34
  const { dataTypes, matchingOnlyParentPost } = params;
35
35
 
36
36
  // API-FIX: parameters should be querystring. (1)
@@ -39,6 +39,7 @@ export const queryPosts = async (
39
39
  const { data } = await client.http.get<Amity.PostPayload & Amity.Pagination>(`/api/v4/posts`, {
40
40
  params: {
41
41
  ...params,
42
+ isDeleted: includeDeleted === false ? false : undefined,
42
43
  /*
43
44
  * when creating post like image, file, video BE will create 2 posts
44
45
  * 1. parent post to store text with dataType=text