@amityco/ts-sdk 0.0.1-beta.30 → 0.0.1-beta.31

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 (181) hide show
  1. package/dist/@types/core/cache.d.ts +3 -0
  2. package/dist/@types/core/cache.d.ts.map +1 -1
  3. package/dist/@types/core/errors.d.ts +2 -1
  4. package/dist/@types/core/errors.d.ts.map +1 -1
  5. package/dist/@types/core/events.d.ts +2 -0
  6. package/dist/@types/core/events.d.ts.map +1 -1
  7. package/dist/@types/core/live.d.ts +8 -4
  8. package/dist/@types/core/live.d.ts.map +1 -1
  9. package/dist/@types/core/query.d.ts +1 -1
  10. package/dist/@types/core/query.d.ts.map +1 -1
  11. package/dist/@types/domains/channel.d.ts +1 -0
  12. package/dist/@types/domains/channel.d.ts.map +1 -1
  13. package/dist/@types/domains/message.d.ts +13 -6
  14. package/dist/@types/domains/message.d.ts.map +1 -1
  15. package/dist/@types/domains/post.d.ts +10 -3
  16. package/dist/@types/domains/post.d.ts.map +1 -1
  17. package/dist/@types/domains/user.d.ts +1 -0
  18. package/dist/@types/domains/user.d.ts.map +1 -1
  19. package/dist/@types/index.d.ts +2 -2
  20. package/dist/@types/index.d.ts.map +1 -1
  21. package/dist/cache/api/isInTombstone.d.ts +18 -0
  22. package/dist/cache/api/isInTombstone.d.ts.map +1 -0
  23. package/dist/cache/api/pushToTombstone.d.ts +17 -0
  24. package/dist/cache/api/pushToTombstone.d.ts.map +1 -0
  25. package/dist/cache/utils/index.d.ts +17 -0
  26. package/dist/cache/utils/index.d.ts.map +1 -1
  27. package/dist/channel/api/queryChannelMembers.d.ts +1 -1
  28. package/dist/channel/api/queryChannelMembers.d.ts.map +1 -1
  29. package/dist/channel/api/queryChannels.d.ts +1 -1
  30. package/dist/channel/api/queryChannels.d.ts.map +1 -1
  31. package/dist/channel/api/tests/queryChannelMembers.test.d.ts +2 -0
  32. package/dist/channel/api/tests/queryChannelMembers.test.d.ts.map +1 -0
  33. package/dist/channel/api/tests/queryChannels.test.d.ts +2 -0
  34. package/dist/channel/api/tests/queryChannels.test.d.ts.map +1 -0
  35. package/dist/channel/events/index.d.ts +2 -0
  36. package/dist/channel/events/index.d.ts.map +1 -1
  37. package/dist/channel/events/onChannelMemberBanned.d.ts +17 -0
  38. package/dist/channel/events/onChannelMemberBanned.d.ts.map +1 -0
  39. package/dist/channel/events/onChannelMemberUnbanned.d.ts +17 -0
  40. package/dist/channel/events/onChannelMemberUnbanned.d.ts.map +1 -0
  41. package/dist/channel/events/tests/onChannelMemberBanned.test.d.ts +2 -0
  42. package/dist/channel/events/tests/onChannelMemberBanned.test.d.ts.map +1 -0
  43. package/dist/channel/events/tests/onChannelMemberUnbanned.test.d.ts +2 -0
  44. package/dist/channel/events/tests/onChannelMemberUnbanned.test.d.ts.map +1 -0
  45. package/dist/channel/observers/liveChannelMembers.d.ts.map +1 -1
  46. package/dist/channel/observers/liveChannels.d.ts.map +1 -1
  47. package/dist/channel/{tests/utils → utils/tests}/prepareChannelPayload.test.d.ts +0 -0
  48. package/dist/channel/utils/tests/prepareChannelPayload.test.d.ts.map +1 -0
  49. package/dist/core/events.d.ts +3 -3
  50. package/dist/core/events.d.ts.map +1 -1
  51. package/dist/core/query/filtering.d.ts +11 -0
  52. package/dist/core/query/filtering.d.ts.map +1 -1
  53. package/dist/core/query/query.d.ts.map +1 -1
  54. package/dist/index.cjs.js +963 -507
  55. package/dist/index.esm.js +946 -499
  56. package/dist/index.umd.js +4 -4
  57. package/dist/message/api/createMessage.d.ts +4 -1
  58. package/dist/message/api/createMessage.d.ts.map +1 -1
  59. package/dist/message/api/deleteMessage.d.ts.map +1 -1
  60. package/dist/message/api/getMessage.d.ts +1 -1
  61. package/dist/message/api/getMessage.d.ts.map +1 -1
  62. package/dist/message/api/getMessages.d.ts.map +1 -1
  63. package/dist/message/api/queryMessages.d.ts.map +1 -1
  64. package/dist/message/api/tests/createMessage.test.d.ts +2 -0
  65. package/dist/message/api/tests/createMessage.test.d.ts.map +1 -0
  66. package/dist/message/api/updateMessage.d.ts.map +1 -1
  67. package/dist/message/events/index.d.ts +1 -0
  68. package/dist/message/events/index.d.ts.map +1 -1
  69. package/dist/message/events/onMessageFetched.d.ts +17 -0
  70. package/dist/message/events/onMessageFetched.d.ts.map +1 -0
  71. package/dist/message/observers/index.d.ts +2 -1
  72. package/dist/message/observers/index.d.ts.map +1 -1
  73. package/dist/message/observers/liveMessage.d.ts +18 -0
  74. package/dist/message/observers/liveMessage.d.ts.map +1 -0
  75. package/dist/message/observers/liveMessages.d.ts.map +1 -1
  76. package/dist/message/observers/observeMessage.d.ts.map +1 -1
  77. package/dist/message/tests/api/getMessage.test.d.ts +2 -0
  78. package/dist/message/tests/api/getMessage.test.d.ts.map +1 -0
  79. package/dist/message/tests/observers/liveMessage.test.d.ts +2 -0
  80. package/dist/message/tests/observers/liveMessage.test.d.ts.map +1 -0
  81. package/dist/message/{observers/tests → tests/observers}/liveMessages.test.d.ts +0 -0
  82. package/dist/message/tests/observers/liveMessages.test.d.ts.map +1 -0
  83. package/dist/post/api/deletePost.d.ts.map +1 -1
  84. package/dist/post/api/getPost.d.ts.map +1 -1
  85. package/dist/post/api/updatePost.d.ts +6 -1
  86. package/dist/post/api/updatePost.d.ts.map +1 -1
  87. package/dist/user/api/getUser.d.ts +1 -1
  88. package/dist/user/api/getUser.d.ts.map +1 -1
  89. package/dist/user/api/getUsers.d.ts.map +1 -1
  90. package/dist/user/api/queryUsers.d.ts.map +1 -1
  91. package/dist/user/api/updateUser.d.ts.map +1 -1
  92. package/dist/user/events/index.d.ts +1 -0
  93. package/dist/user/events/index.d.ts.map +1 -1
  94. package/dist/user/events/onUserFetched.d.ts +17 -0
  95. package/dist/user/events/onUserFetched.d.ts.map +1 -0
  96. package/dist/user/observers/index.d.ts +1 -0
  97. package/dist/user/observers/index.d.ts.map +1 -1
  98. package/dist/user/observers/liveUser.d.ts +18 -0
  99. package/dist/user/observers/liveUser.d.ts.map +1 -0
  100. package/dist/user/observers/observeUser.d.ts.map +1 -1
  101. package/dist/user/tests/api/getUser.test.d.ts +2 -0
  102. package/dist/user/tests/api/getUser.test.d.ts.map +1 -0
  103. package/dist/user/tests/observers/liveUser.test.d.ts +2 -0
  104. package/dist/user/tests/observers/liveUser.test.d.ts.map +1 -0
  105. package/dist/utils/constants.d.ts +3 -0
  106. package/dist/utils/constants.d.ts.map +1 -1
  107. package/dist/utils/tests/dummy/channel.d.ts +15 -0
  108. package/dist/utils/tests/dummy/channel.d.ts.map +1 -1
  109. package/dist/utils/tests/dummy/message.d.ts +12 -1
  110. package/dist/utils/tests/dummy/message.d.ts.map +1 -1
  111. package/dist/utils/tests/dummy/user.d.ts +10 -0
  112. package/dist/utils/tests/dummy/user.d.ts.map +1 -1
  113. package/package.json +1 -1
  114. package/src/@types/core/cache.ts +4 -0
  115. package/src/@types/core/errors.ts +1 -0
  116. package/src/@types/core/events.ts +2 -0
  117. package/src/@types/core/live.ts +10 -4
  118. package/src/@types/core/query.ts +1 -1
  119. package/src/@types/domains/channel.ts +6 -0
  120. package/src/@types/domains/message.ts +13 -10
  121. package/src/@types/domains/post.ts +10 -3
  122. package/src/@types/domains/user.ts +8 -0
  123. package/src/@types/index.ts +2 -2
  124. package/src/cache/api/index.ts +2 -1
  125. package/src/cache/api/isInTombstone.ts +41 -0
  126. package/src/cache/api/pushToTombstone.ts +33 -0
  127. package/src/cache/utils/index.ts +22 -0
  128. package/src/channel/api/queryChannelMembers.ts +53 -5
  129. package/src/channel/api/queryChannels.ts +31 -4
  130. package/src/channel/api/tests/queryChannelMembers.test.ts +115 -0
  131. package/src/channel/api/tests/queryChannels.test.ts +101 -0
  132. package/src/channel/events/index.ts +5 -0
  133. package/src/channel/events/onChannelMemberBanned.ts +36 -0
  134. package/src/channel/events/onChannelMemberUnbanned.ts +36 -0
  135. package/src/channel/events/tests/onChannelMemberBanned.test.ts +58 -0
  136. package/src/channel/events/tests/onChannelMemberUnbanned.test.ts +58 -0
  137. package/src/channel/observers/liveChannelMembers.ts +28 -4
  138. package/src/channel/observers/liveChannels.ts +7 -1
  139. package/src/channel/observers/tests/liveChannelMembers.test.ts +53 -11
  140. package/src/channel/observers/tests/liveChannels.test.ts +50 -20
  141. package/src/channel/{tests/utils → utils/tests}/prepareChannelPayload.test.ts +1 -1
  142. package/src/core/events.ts +1 -0
  143. package/src/core/query/filtering.ts +49 -0
  144. package/src/core/query/query.ts +9 -6
  145. package/src/core/tests/query/query.test.ts +12 -2
  146. package/src/message/api/createMessage.ts +15 -9
  147. package/src/message/api/deleteMessage.ts +2 -1
  148. package/src/message/api/getMessage.ts +32 -4
  149. package/src/message/api/getMessages.ts +6 -1
  150. package/src/message/api/queryMessages.ts +3 -0
  151. package/src/message/api/tests/createMessage.test.ts +76 -0
  152. package/src/message/api/updateMessage.ts +2 -0
  153. package/src/message/events/index.ts +1 -0
  154. package/src/message/events/onMessageFetched.ts +30 -0
  155. package/src/message/observers/index.ts +2 -1
  156. package/src/message/observers/liveMessage.ts +100 -0
  157. package/src/message/observers/liveMessages.ts +3 -1
  158. package/src/message/observers/observeMessage.ts +4 -0
  159. package/src/message/tests/api/getMessage.test.ts +96 -0
  160. package/src/message/tests/observers/liveMessage.test.ts +248 -0
  161. package/src/message/{observers/tests → tests/observers}/liveMessages.test.ts +29 -3
  162. package/src/post/api/deletePost.ts +4 -3
  163. package/src/post/api/getPost.ts +8 -4
  164. package/src/post/api/updatePost.ts +6 -1
  165. package/src/user/api/getUser.ts +34 -6
  166. package/src/user/api/getUsers.ts +3 -0
  167. package/src/user/api/queryUsers.ts +3 -0
  168. package/src/user/api/updateUser.ts +3 -0
  169. package/src/user/events/index.ts +1 -0
  170. package/src/user/events/onUserFetched.ts +19 -0
  171. package/src/user/observers/index.ts +1 -0
  172. package/src/user/observers/liveUser.ts +89 -0
  173. package/src/user/observers/observeUser.ts +2 -0
  174. package/src/user/tests/api/getUser.test.ts +112 -0
  175. package/src/user/tests/observers/liveUser.test.ts +224 -0
  176. package/src/utils/constants.ts +5 -0
  177. package/src/utils/tests/dummy/channel.ts +43 -0
  178. package/src/utils/tests/dummy/message.ts +16 -2
  179. package/src/utils/tests/dummy/user.ts +11 -0
  180. package/dist/channel/tests/utils/prepareChannelPayload.test.d.ts.map +0 -1
  181. package/dist/message/observers/tests/liveMessages.test.d.ts.map +0 -1
@@ -1,5 +1,6 @@
1
1
  import { getActiveClient } from '~/client/api';
2
2
 
3
+ import { fireEvent } from '~/core/events';
3
4
  import { pullFromCache } from '~/cache/api';
4
5
  import { ingestInCache } from '~/cache/api/ingestInCache';
5
6
 
@@ -31,8 +32,12 @@ export const getMessages = async (
31
32
  const cachedAt = client.cache && Date.now();
32
33
  if (client.cache) ingestInCache(data, { cachedAt });
33
34
 
35
+ const { messages } = data;
36
+
37
+ fireEvent('v3.message.didFetch', { messages });
38
+
34
39
  return {
35
- data: data.messages,
40
+ data: messages,
36
41
  cachedAt,
37
42
  };
38
43
  };
@@ -1,5 +1,6 @@
1
1
  import { getActiveClient } from '~/client/api';
2
2
 
3
+ import { fireEvent } from '~/core/events';
3
4
  import { toPage, toToken } from '~/core/query';
4
5
  import { pullFromCache, pushToCache } from '~/cache/api';
5
6
  import { ingestInCache } from '~/cache/api/ingestInCache';
@@ -62,6 +63,8 @@ export const queryMessages = async (
62
63
  pushToCache(cacheKey, { messages: messages.map(getResolver('message')), paging });
63
64
  }
64
65
 
66
+ fireEvent('v3.message.didFetch', { messages });
67
+
65
68
  const nextPage = toPage(paging.next);
66
69
  const prevPage = toPage(paging.previous);
67
70
 
@@ -0,0 +1,76 @@
1
+ import { UNSYNCED_OBJECT_CACHED_AT_VALUE } from '~/utils/constants';
2
+ import { disableCache, enableCache, pullFromCache } from '~/cache/api';
3
+
4
+ import { client, message11 } from '~/utils/tests';
5
+
6
+ import { onMessageCreated } from '../../events';
7
+ import { createMessage } from '../createMessage';
8
+
9
+ describe('message/api', () => {
10
+ const message = message11;
11
+ const { messageId, ...messageBundle } = message11;
12
+
13
+ const getResolvedMessageValue = () => ({
14
+ data: {
15
+ messages: [message],
16
+ users: [],
17
+ files: [],
18
+ },
19
+ });
20
+
21
+ describe('createMessage', () => {
22
+ test('should return message', async () => {
23
+ client.http.post = jest.fn().mockResolvedValue(getResolvedMessageValue());
24
+ const { data } = await createMessage(messageBundle);
25
+ expect(data).toStrictEqual(message);
26
+ });
27
+
28
+ test('should throw an error if request fails', async () => {
29
+ client.http.post = jest.fn().mockRejectedValueOnce(new Error('error'));
30
+ await expect(createMessage(messageBundle)).rejects.toThrow('error');
31
+ });
32
+ });
33
+
34
+ describe('createMessage.optimistically', () => {
35
+ beforeEach(() => enableCache());
36
+ afterEach(() => disableCache());
37
+
38
+ test('should create cache after created message', () => {
39
+ const optimisticId = createMessage.optimistically(messageBundle)?.data?.messageId;
40
+ expect(pullFromCache(['message', 'get', optimisticId])?.data).toBeDefined();
41
+ });
42
+
43
+ test('should increase message count after created message', () => {
44
+ const { channelId } = messageBundle;
45
+
46
+ const currentCount =
47
+ pullFromCache<Amity.Channel>(['channel', 'get', channelId])?.data?.messageCount ?? 0;
48
+
49
+ createMessage.optimistically(messageBundle);
50
+
51
+ expect(
52
+ pullFromCache<Amity.Channel>(['channel', 'get', channelId])?.data?.messageCount,
53
+ ).toEqual(currentCount + 1);
54
+ });
55
+
56
+ test('should fire event `onMessageCreated`', () => {
57
+ expect.assertions(1);
58
+ let dispose;
59
+
60
+ const callback = new Promise(resolve => {
61
+ dispose = onMessageCreated(resolve);
62
+ }).finally(dispose);
63
+
64
+ createMessage.optimistically(messageBundle);
65
+ return expect(callback).resolves.not.toThrow();
66
+ });
67
+
68
+ test('should define object as unsynced object', async () => {
69
+ const data = { channelId: 'test', data: { text: 'text' } };
70
+
71
+ const message = createMessage.optimistically(data);
72
+
73
+ expect(message?.cachedAt).toEqual(UNSYNCED_OBJECT_CACHED_AT_VALUE);
74
+ });
75
+ });
76
+ });
@@ -38,6 +38,8 @@ export const updateMessage = async (
38
38
 
39
39
  const { messages } = data;
40
40
 
41
+ fireEvent('v3.message.didUpdate', { messages });
42
+
41
43
  return {
42
44
  data: messages.find(message => message.messageId === messageId)!,
43
45
  cachedAt,
@@ -1,3 +1,4 @@
1
1
  export * from './onMessageCreated';
2
2
  export * from './onMessageUpdated';
3
3
  export * from './onMessageDeleted';
4
+ export * from './onMessageFetched';
@@ -0,0 +1,30 @@
1
+ import { getActiveClient } from '~/client/api';
2
+ import { createEventSubscriber } from '~/core/events';
3
+
4
+ import { ingestInCache } from '~/cache/api/ingestInCache';
5
+
6
+ /**
7
+ * ```js
8
+ * import { onMessageFetched } from '@amityco/ts-sdk'
9
+ * const dispose = onMessageFetched(message => {
10
+ * // ...
11
+ * })
12
+ * ```
13
+ *
14
+ * Fired when an {@link Amity.Message} has been fetched
15
+ *
16
+ * @param callback The function to call when the event was fired
17
+ * @returns an {@link Amity.Unsubscriber} function to stop listening
18
+ *
19
+ * @category Message Events
20
+ */
21
+ export const onMessageFetched = (callback: Amity.Listener<Amity.Message>): Amity.Unsubscriber => {
22
+ const client = getActiveClient();
23
+
24
+ const filter = (payload: Amity.MessagePayload) => {
25
+ ingestInCache(payload);
26
+ callback(payload.messages[0]);
27
+ };
28
+
29
+ return createEventSubscriber(client, 'message/onMessageFetched', 'v3.message.didFetch', filter);
30
+ };
@@ -1,3 +1,4 @@
1
+ export { liveMessage } from './liveMessage';
2
+ export { liveMessages } from './liveMessages';
1
3
  export { observeMessage } from './observeMessage';
2
4
  export { observeMessages } from './observeMessages';
3
- export { liveMessages } from './liveMessages';
@@ -0,0 +1,100 @@
1
+ import { ASCApiError } from '~/core/errors';
2
+ import { getActiveClient } from '~/client/api';
3
+ import { createQuery, runQuery } from '~/core/query';
4
+ import {
5
+ UNSYNCED_OBJECT_CACHED_AT_VALUE,
6
+ LIVE_OBJECT_ENABLE_CACHE_MESSAGE,
7
+ UNSYNCED_OBJECT_CACHED_AT_MESSAGE,
8
+ } from '~/utils/constants';
9
+
10
+ import { getMessage } from '../api';
11
+ import { onMessageUpdated, onMessageDeleted, onMessageFetched } from '../events';
12
+
13
+ /**
14
+ * ```js
15
+ * import { liveMessage } from '@amityco/ts-sdk'
16
+ *
17
+ * let messages = []
18
+ * const unsub = liveMessage(messageId, response => merge(messages, response.data))
19
+ * ```
20
+ *
21
+ * Observe all mutation on a given {@link Amity.Message}
22
+ *
23
+ * @param messageId the ID of the message to observe
24
+ * @param callback the function to call when new data are available
25
+ * @returns An {@link Amity.Unsubscriber} function to run when willing to stop observing the messages
26
+ *
27
+ * @category Message Live Object
28
+ */
29
+ export const liveMessage = (
30
+ messageId: Amity.Message['messageId'],
31
+ callback: Amity.LiveObjectCallback<Amity.Message>,
32
+ ): Amity.Unsubscriber => {
33
+ const { log, cache } = getActiveClient();
34
+ let message: Amity.Message;
35
+ let isUnsyncedObject = false;
36
+
37
+ if (!cache) {
38
+ console.log(LIVE_OBJECT_ENABLE_CACHE_MESSAGE);
39
+ }
40
+
41
+ const timestamp = Date.now();
42
+ log(`liveMessage(tmpid: ${timestamp}) > listen`);
43
+
44
+ const disposers: Amity.Unsubscriber[] = [];
45
+
46
+ const dispatcher = (data: Amity.LiveObject<Amity.Message>) => {
47
+ // we check updatedAt to see if the current snapshot is new so
48
+ // we won't send old data back, only fresh new data
49
+ if (message && data.data?.updatedAt === message?.updatedAt && data.origin !== 'server') return;
50
+
51
+ callback(data);
52
+ message = data.data;
53
+ };
54
+
55
+ const realtimeRouter = (message: Amity.Message, action: Amity.MessageActionType) => {
56
+ dispatcher({ loading: false, data: message, origin: 'event' });
57
+ };
58
+
59
+ const onFetch = () => {
60
+ const query = createQuery(getMessage, messageId, true);
61
+
62
+ runQuery(query, ({ loading, data, error, origin, cachedAt }) => {
63
+ if (cachedAt === UNSYNCED_OBJECT_CACHED_AT_VALUE) {
64
+ dispatcher({
65
+ data,
66
+ origin,
67
+ loading: false,
68
+ error: new ASCApiError(
69
+ UNSYNCED_OBJECT_CACHED_AT_MESSAGE,
70
+ Amity.ClientError.DISALOOW_UNSYNCED_OBJECT,
71
+ Amity.ErrorLevel.ERROR,
72
+ ),
73
+ });
74
+
75
+ isUnsyncedObject = true;
76
+ disposers.forEach(fn => fn());
77
+ } else if (!isUnsyncedObject) {
78
+ dispatcher({ loading, data, origin, error });
79
+ }
80
+
81
+ if (error) {
82
+ // Terminate observe functionality on error
83
+ disposers.forEach(fn => fn());
84
+ }
85
+ });
86
+ };
87
+
88
+ disposers.push(
89
+ onMessageFetched(message => realtimeRouter(message, 'onFetch')),
90
+ onMessageUpdated(message => realtimeRouter(message, 'onUpdate')),
91
+ onMessageDeleted(message => realtimeRouter(message, 'onDelete')),
92
+ );
93
+
94
+ onFetch();
95
+
96
+ return () => {
97
+ log(`liveMessage(tmpid: ${timestamp}) > dispose`);
98
+ disposers.forEach(fn => fn());
99
+ };
100
+ };
@@ -10,7 +10,7 @@ import {
10
10
  ENABLE_CACHE_MESSAGE,
11
11
  } from '~/utils/constants';
12
12
 
13
- import { onMessageCreated, onMessageDeleted, onMessageUpdated } from '../events';
13
+ import { onMessageCreated, onMessageDeleted, onMessageUpdated, onMessageFetched } from '../events';
14
14
  import { queryMessages } from '../api';
15
15
 
16
16
  /**
@@ -126,6 +126,8 @@ export const liveMessages = (
126
126
  };
127
127
 
128
128
  disposers.push(
129
+ onMessageFetched(message => realtimeRouter(message, 'onFetch')),
130
+
129
131
  onMessageCreated(message => realtimeRouter(message, 'onCreate')),
130
132
  onMessageDeleted(message => realtimeRouter(message, 'onDelete')),
131
133
 
@@ -34,6 +34,10 @@ export const observeMessage = <Events extends ['onFetch', 'onUpdate', 'onDelete'
34
34
  const timestamp = Date.now();
35
35
  log(`observeMessage(tmpid: ${timestamp}) > listen`);
36
36
 
37
+ console.log(
38
+ 'observeMessage will be deprecated on 15th November 2022, please use liveMessage instead.',
39
+ );
40
+
37
41
  // wrapper function to make sure
38
42
  const router = (result: Amity.Snapshot<Amity.Message>, action: Events[number]) => {
39
43
  // filter function
@@ -0,0 +1,96 @@
1
+ import { ASCApiError } from '~/core/errors';
2
+ import { disableCache, enableCache, pushToCache } from '~/cache/api';
3
+ import { pushToTombstone } from '~/cache/api/pushToTombstone';
4
+ import { isInTombstone } from '~/cache/api/isInTombstone';
5
+
6
+ import { client, message11 } from '~/utils/tests';
7
+
8
+ import { getMessage } from '../../api/getMessage';
9
+
10
+ const message = message11;
11
+ const getResolvedMessageValue = () => ({
12
+ data: {
13
+ messages: [message],
14
+ users: [],
15
+ files: [],
16
+ },
17
+ });
18
+
19
+ describe('getMessage', () => {
20
+ test('it should return message', async () => {
21
+ client.http.get = jest.fn().mockResolvedValue(getResolvedMessageValue());
22
+
23
+ const { data } = await getMessage(message.messageId);
24
+
25
+ expect(data).toStrictEqual(message);
26
+ });
27
+
28
+ test('it should update cache after fetching message from server', async () => {
29
+ enableCache();
30
+ client.http.get = jest.fn().mockResolvedValue(getResolvedMessageValue());
31
+ await getMessage(message.messageId);
32
+
33
+ expect(getMessage.locally(message.messageId)).toBeDefined();
34
+
35
+ disableCache();
36
+ });
37
+
38
+ test('it should throw an error if request fails', async () => {
39
+ client.http.get = jest.fn().mockRejectedValueOnce(new Error('error'));
40
+
41
+ await expect(getMessage(message.messageId)).rejects.toThrow('error');
42
+ });
43
+ });
44
+
45
+ describe('getMessage locally', () => {
46
+ test('it should return cached message', async () => {
47
+ enableCache();
48
+ pushToCache(['message', 'get', message.messageId], message, { cachedAt: Date.now() });
49
+
50
+ client.http.get = jest
51
+ .fn()
52
+ .mockRejectedValueOnce(
53
+ new ASCApiError('not found!', Amity.ServerError.ITEM_NOT_FOUND, Amity.ErrorLevel.ERROR),
54
+ );
55
+
56
+ await expect(getMessage(message.messageId)).rejects.toThrow();
57
+
58
+ expect(getMessage.locally(message.messageId)).toBeUndefined();
59
+ disableCache();
60
+ });
61
+
62
+ test('it should return undefined if message not in cache', () => {
63
+ expect(getMessage.locally('non-existent-message')).toBeUndefined();
64
+ });
65
+
66
+ test('it should return undefined if cache not enabled', () => {
67
+ expect(getMessage.locally(message.messageId)).toBeUndefined();
68
+ });
69
+ });
70
+
71
+ describe('getMessage tombostone', () => {
72
+ test('it should add message to tombostone if 404', async () => {
73
+ enableCache();
74
+ client.http.get = jest
75
+ .fn()
76
+ .mockRejectedValueOnce(
77
+ new ASCApiError('not found!', Amity.ServerError.ITEM_NOT_FOUND, Amity.ErrorLevel.ERROR),
78
+ );
79
+
80
+ await expect(getMessage(message.messageId)).rejects.toThrow();
81
+ expect(() => {
82
+ isInTombstone('message', message.messageId);
83
+ }).toThrow();
84
+
85
+ disableCache();
86
+ });
87
+
88
+ test('it should throw error if message not found in tombostone', async () => {
89
+ enableCache();
90
+
91
+ pushToTombstone('message', message.messageId);
92
+ await expect(getMessage(message.messageId)).rejects.toThrow();
93
+
94
+ disableCache();
95
+ });
96
+ });
@@ -0,0 +1,248 @@
1
+ import { ASCApiError } from '~/core/errors';
2
+ import { disableCache, enableCache } from '~/cache/api';
3
+ import {
4
+ LIVE_OBJECT_ENABLE_CACHE_MESSAGE,
5
+ UNSYNCED_OBJECT_CACHED_AT_MESSAGE,
6
+ } from '~/utils/constants';
7
+ import { liveMessage } from '../../observers/liveMessage';
8
+ import { createMessage } from '../../api';
9
+
10
+ import {
11
+ pause,
12
+ client,
13
+ connectClient,
14
+ disconnectClient,
15
+ messageQueryResponse,
16
+ message11 as defaultMessage,
17
+ } from '~/utils/tests';
18
+
19
+ const getSnapshot = () => {
20
+ return {
21
+ loading: true,
22
+ error: undefined as any,
23
+ data: undefined as undefined | Amity.Message,
24
+ };
25
+ };
26
+
27
+ describe('liveMessage', () => {
28
+ beforeAll(async () => {
29
+ await connectClient();
30
+ });
31
+
32
+ afterAll(async () => {
33
+ await disconnectClient();
34
+ });
35
+
36
+ beforeEach(async () => {
37
+ enableCache();
38
+ });
39
+
40
+ afterEach(disableCache);
41
+
42
+ test('it should show message if cache not enabled', () => {
43
+ /*
44
+ * NOTE: at the time of writing this test cache is diabled by default, but
45
+ * there is a proposal to enable cache by default. So I'm disabling cache
46
+ * here to ensure cache is disabled
47
+ */
48
+ disableCache();
49
+
50
+ const callback = jest.fn();
51
+ jest.spyOn(global.console, 'log');
52
+ client.http.get = jest.fn();
53
+
54
+ // call liveMessage check if mocked console get's called with the correct
55
+ // message
56
+ client.use();
57
+ liveMessage(defaultMessage.messageId, callback);
58
+
59
+ expect(console.log).toBeCalledWith(LIVE_OBJECT_ENABLE_CACHE_MESSAGE);
60
+ });
61
+
62
+ test('it should return message live object', async () => {
63
+ const snapshot = getSnapshot();
64
+
65
+ // mock response of queryMessages and validate
66
+ const callback = jest.fn();
67
+ client.http.get = jest.fn().mockResolvedValue(messageQueryResponse);
68
+
69
+ liveMessage(defaultMessage.messageId, callback);
70
+
71
+ expect(callback).toHaveBeenCalledTimes(1);
72
+
73
+ // check if cache data returned (should be empty)
74
+ expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
75
+
76
+ await pause();
77
+
78
+ expect(callback).toHaveBeenCalledTimes(2);
79
+
80
+ snapshot.loading = false;
81
+ snapshot.data = defaultMessage;
82
+
83
+ expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
84
+ });
85
+
86
+ test('it should return error on api failure', async () => {
87
+ const snapshot = getSnapshot();
88
+ const error = new ASCApiError(
89
+ 'server error!',
90
+ Amity.ServerError.ITEM_NOT_FOUND,
91
+ Amity.ErrorLevel.ERROR,
92
+ );
93
+
94
+ // mock response of queryMessages and validate
95
+ const callback = jest.fn();
96
+ client.http.get = jest.fn().mockRejectedValue(error);
97
+
98
+ liveMessage(defaultMessage.messageId, callback);
99
+
100
+ expect(callback).toHaveBeenCalledTimes(1);
101
+
102
+ // check if cache data returned (should be empty)
103
+ expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
104
+
105
+ await pause();
106
+
107
+ expect(callback).toHaveBeenCalledTimes(2);
108
+
109
+ snapshot.error = error;
110
+ snapshot.loading = false;
111
+ expect(callback).toHaveBeenLastCalledWith(expect.objectContaining(snapshot));
112
+ });
113
+
114
+ test('it should terminate updates on api failure', async () => {
115
+ const snapshot = getSnapshot();
116
+ const error = new ASCApiError(
117
+ 'server error!',
118
+ Amity.ServerError.ITEM_NOT_FOUND,
119
+ Amity.ErrorLevel.ERROR,
120
+ );
121
+
122
+ // mock response of queryMessages and validate
123
+ const callback = jest.fn();
124
+ client.http.get = jest.fn().mockRejectedValue(error);
125
+
126
+ liveMessage(defaultMessage.messageId, callback);
127
+
128
+ expect(callback).toHaveBeenCalledTimes(1);
129
+
130
+ // check if cache data returned (should be empty)
131
+ expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
132
+
133
+ await pause();
134
+
135
+ expect(callback).toHaveBeenCalledTimes(2);
136
+
137
+ snapshot.error = error;
138
+ snapshot.loading = false;
139
+ expect(callback).toHaveBeenLastCalledWith(expect.objectContaining(snapshot));
140
+
141
+ const updatedAt = new Date(defaultMessage?.updatedAt ?? new Date());
142
+ updatedAt.setDate(updatedAt.getDate() + 7);
143
+
144
+ const updatedMessage = {
145
+ ...defaultMessage,
146
+ updatedAt: updatedAt.toISOString(),
147
+ };
148
+ client.emitter.emit('v3.message.didUpdate', { messages: [updatedMessage] });
149
+
150
+ delete snapshot.error;
151
+ snapshot.loading = false;
152
+ snapshot.data = updatedMessage;
153
+ expect(callback).not.toHaveBeenNthCalledWith(3, expect.objectContaining(snapshot));
154
+ });
155
+
156
+ test('it should not return updates on obtain once!', async () => {
157
+ const snapshot = getSnapshot();
158
+
159
+ // mock response of queryMessages and validate
160
+ const callback = jest.fn();
161
+ client.http.get = jest.fn().mockResolvedValue(messageQueryResponse);
162
+
163
+ const unsub = liveMessage(defaultMessage.messageId, callback);
164
+
165
+ expect(callback).toHaveBeenCalledTimes(1);
166
+
167
+ // check if cache data returned (should be empty)
168
+ expect(callback).toHaveBeenCalledWith(expect.objectContaining(snapshot));
169
+
170
+ await pause();
171
+
172
+ expect(callback).toHaveBeenCalledTimes(2);
173
+
174
+ unsub();
175
+
176
+ const updatedAt = new Date(defaultMessage?.updatedAt ?? new Date());
177
+ updatedAt.setDate(updatedAt.getDate() + 7);
178
+
179
+ const updatedMessage = {
180
+ ...defaultMessage,
181
+ updatedAt: updatedAt.toISOString(),
182
+ };
183
+ client.emitter.emit('v3.message.didUpdate', { messages: [updatedMessage] });
184
+
185
+ delete snapshot.error;
186
+ snapshot.loading = false;
187
+ snapshot.data = updatedMessage;
188
+ expect(callback).not.toHaveBeenNthCalledWith(3, expect.objectContaining(snapshot));
189
+ });
190
+
191
+ const events: [string, keyof Amity.Events][] = [
192
+ ['it should return updated message on onFetch', 'v3.message.didFetch'],
193
+ ['it should return updated message on onUpdate', 'v3.message.didUpdate'],
194
+ ['it should return updated message on onDelete', 'v3.message.didDelete'],
195
+ ];
196
+
197
+ test.each(events)('%s', async (test, event) => {
198
+ const snapshot = getSnapshot();
199
+
200
+ // mock response of queryMessages and validate
201
+ const callback = jest.fn();
202
+ client.http.get = jest.fn().mockResolvedValue(messageQueryResponse);
203
+
204
+ liveMessage(defaultMessage.messageId, callback);
205
+
206
+ await pause();
207
+
208
+ expect(callback).toHaveBeenCalledTimes(2);
209
+
210
+ const updatedAt = new Date(defaultMessage?.updatedAt ?? new Date());
211
+ updatedAt.setDate(updatedAt.getDate() + 7);
212
+
213
+ const updatedMessage = {
214
+ ...defaultMessage,
215
+ updatedAt: updatedAt.toISOString(),
216
+ };
217
+ client.emitter.emit(event, { messages: [updatedMessage] });
218
+
219
+ delete snapshot.error;
220
+ snapshot.loading = false;
221
+ snapshot.data = updatedMessage;
222
+ expect(callback).toHaveBeenNthCalledWith(3, expect.objectContaining(snapshot));
223
+ });
224
+
225
+ test('it should terminate updates for unsynced object', async () => {
226
+ const callback = jest.fn();
227
+ client.http.get = jest.fn();
228
+ const snapshot = getSnapshot();
229
+ const data = { channelId: 'test', data: { text: 'text' } };
230
+
231
+ const message = createMessage.optimistically(data);
232
+
233
+ const error = new ASCApiError(
234
+ UNSYNCED_OBJECT_CACHED_AT_MESSAGE,
235
+ Amity.ClientError.DISALOOW_UNSYNCED_OBJECT,
236
+ Amity.ErrorLevel.ERROR,
237
+ );
238
+
239
+ liveMessage(message!.data.messageId, callback);
240
+
241
+ expect(callback).toHaveBeenCalledTimes(1);
242
+
243
+ snapshot.error = error;
244
+ snapshot.loading = false;
245
+ snapshot.data = message!.data;
246
+ expect(callback).toHaveBeenLastCalledWith(expect.objectContaining(snapshot));
247
+ });
248
+ });
@@ -2,15 +2,15 @@ import { ASCApiError } from '~/core/errors';
2
2
  import { disableCache, enableCache } from '~/cache/api';
3
3
  import { ENABLE_CACHE_MESSAGE } from '~/utils/constants';
4
4
 
5
- import { liveMessages } from '../liveMessages';
6
- import { queryMessages } from '~/message/api';
5
+ import { queryMessages } from '../../api';
6
+ import { liveMessages } from '../../observers';
7
7
 
8
8
  import {
9
9
  client,
10
10
  connectClient,
11
11
  disconnectClient,
12
12
  messages,
13
- message as defaultMessage,
13
+ message11 as defaultMessage,
14
14
  } from '~/utils/tests';
15
15
 
16
16
  describe('liveMessages', () => {
@@ -222,6 +222,32 @@ describe('liveMessages', () => {
222
222
  );
223
223
  });
224
224
 
225
+ test('it should add new message to collection onFetch', async () => {
226
+ const callback = jest.fn();
227
+ client.http.get = jest.fn().mockResolvedValue({
228
+ data: {
229
+ messages: returnValue,
230
+ paging: {},
231
+ },
232
+ });
233
+
234
+ liveMessages({ channelId }, callback);
235
+
236
+ await expect(queryMessages({ channelId })).resolves.toBeTruthy();
237
+ expect(callback).toHaveBeenCalledTimes(2);
238
+
239
+ client.emitter.emit('v3.message.didFetch', { messages: [message] });
240
+
241
+ expect(callback).toHaveBeenNthCalledWith(
242
+ 3,
243
+ expect.objectContaining({
244
+ data: [message],
245
+ error: undefined,
246
+ loading: false,
247
+ }),
248
+ );
249
+ });
250
+
225
251
  test('it should add new message to collection onCreate', async () => {
226
252
  const newMessage = {
227
253
  ...message,