stream-chat-react-native-core 7.0.1 → 7.1.0

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 (409) hide show
  1. package/README.md +1 -1
  2. package/lib/commonjs/components/Channel/Channel.js +214 -237
  3. package/lib/commonjs/components/Channel/Channel.js.map +1 -1
  4. package/lib/commonjs/components/ChannelList/ChannelList.js +2 -19
  5. package/lib/commonjs/components/ChannelList/ChannelList.js.map +1 -1
  6. package/lib/commonjs/components/ChannelList/hooks/usePaginatedChannels.js +48 -176
  7. package/lib/commonjs/components/ChannelList/hooks/usePaginatedChannels.js.map +1 -1
  8. package/lib/commonjs/components/ChannelPreview/hooks/useIsChannelMuted.js +13 -2
  9. package/lib/commonjs/components/ChannelPreview/hooks/useIsChannelMuted.js.map +1 -1
  10. package/lib/commonjs/components/Chat/Chat.js +33 -46
  11. package/lib/commonjs/components/Chat/Chat.js.map +1 -1
  12. package/lib/commonjs/components/Chat/hooks/useAppSettings.js +47 -120
  13. package/lib/commonjs/components/Chat/hooks/useAppSettings.js.map +1 -1
  14. package/lib/commonjs/components/Chat/hooks/useSyncDatabase.js +0 -15
  15. package/lib/commonjs/components/Chat/hooks/useSyncDatabase.js.map +1 -1
  16. package/lib/commonjs/components/MessageMenu/MessageUserReactions.js +5 -0
  17. package/lib/commonjs/components/MessageMenu/MessageUserReactions.js.map +1 -1
  18. package/lib/commonjs/components/MessageMenu/hooks/useFetchReactions.js +92 -108
  19. package/lib/commonjs/components/MessageMenu/hooks/useFetchReactions.js.map +1 -1
  20. package/lib/commonjs/index.js +8 -0
  21. package/lib/commonjs/index.js.map +1 -1
  22. package/lib/commonjs/mock-builders/event/channelVisible.js +13 -0
  23. package/lib/commonjs/mock-builders/event/channelVisible.js.map +1 -0
  24. package/lib/commonjs/mock-builders/event/memberAdded.js +4 -2
  25. package/lib/commonjs/mock-builders/event/memberAdded.js.map +1 -1
  26. package/lib/commonjs/mock-builders/event/memberRemoved.js +2 -1
  27. package/lib/commonjs/mock-builders/event/memberRemoved.js.map +1 -1
  28. package/lib/commonjs/mock-builders/event/memberUpdated.js +2 -1
  29. package/lib/commonjs/mock-builders/event/memberUpdated.js.map +1 -1
  30. package/lib/commonjs/mock-builders/event/messageNew.js +4 -2
  31. package/lib/commonjs/mock-builders/event/messageNew.js.map +1 -1
  32. package/lib/commonjs/mock-builders/event/messageRead.js +6 -3
  33. package/lib/commonjs/mock-builders/event/messageRead.js.map +1 -1
  34. package/lib/commonjs/mock-builders/event/notificationMarkUnread.js +3 -0
  35. package/lib/commonjs/mock-builders/event/notificationMarkUnread.js.map +1 -1
  36. package/lib/commonjs/mock-builders/generator/channel.js +10 -3
  37. package/lib/commonjs/mock-builders/generator/channel.js.map +1 -1
  38. package/lib/commonjs/store/OfflineDB.js +108 -0
  39. package/lib/commonjs/store/OfflineDB.js.map +1 -0
  40. package/lib/commonjs/store/SqliteClient.js +11 -10
  41. package/lib/commonjs/store/SqliteClient.js.map +1 -1
  42. package/lib/commonjs/store/apis/addPendingTask.js.map +1 -1
  43. package/lib/commonjs/store/apis/channelExists.js +34 -0
  44. package/lib/commonjs/store/apis/channelExists.js.map +1 -0
  45. package/lib/commonjs/store/apis/deleteChannel.js +4 -4
  46. package/lib/commonjs/store/apis/deleteChannel.js.map +1 -1
  47. package/lib/commonjs/store/apis/deleteMember.js +4 -4
  48. package/lib/commonjs/store/apis/deleteMember.js.map +1 -1
  49. package/lib/commonjs/store/apis/deleteMessage.js +16 -12
  50. package/lib/commonjs/store/apis/deleteMessage.js.map +1 -1
  51. package/lib/commonjs/store/apis/deleteMessagesForChannel.js +11 -12
  52. package/lib/commonjs/store/apis/deleteMessagesForChannel.js.map +1 -1
  53. package/lib/commonjs/store/apis/deleteReaction.js +29 -17
  54. package/lib/commonjs/store/apis/deleteReaction.js.map +1 -1
  55. package/lib/commonjs/store/apis/deleteReactions.js +4 -4
  56. package/lib/commonjs/store/apis/deleteReactions.js.map +1 -1
  57. package/lib/commonjs/store/apis/dropPendingTasks.js +41 -0
  58. package/lib/commonjs/store/apis/dropPendingTasks.js.map +1 -0
  59. package/lib/commonjs/store/apis/getChannelMessages.js +1 -1
  60. package/lib/commonjs/store/apis/getChannelMessages.js.map +1 -1
  61. package/lib/commonjs/store/apis/getChannels.js +18 -21
  62. package/lib/commonjs/store/apis/getChannels.js.map +1 -1
  63. package/lib/commonjs/store/apis/getLastSyncedAt.js +3 -9
  64. package/lib/commonjs/store/apis/getLastSyncedAt.js.map +1 -1
  65. package/lib/commonjs/store/apis/getReactionsforFilterSort.js +5 -8
  66. package/lib/commonjs/store/apis/getReactionsforFilterSort.js.map +1 -1
  67. package/lib/commonjs/store/apis/index.js +103 -4
  68. package/lib/commonjs/store/apis/index.js.map +1 -1
  69. package/lib/commonjs/store/apis/insertReaction.js +4 -4
  70. package/lib/commonjs/store/apis/insertReaction.js.map +1 -1
  71. package/lib/commonjs/store/apis/queries/selectMessagesForChannels.js +1 -1
  72. package/lib/commonjs/store/apis/queries/selectMessagesForChannels.js.map +1 -1
  73. package/lib/commonjs/store/apis/queries/selectReactionsForMessages.js +25 -5
  74. package/lib/commonjs/store/apis/queries/selectReactionsForMessages.js.map +1 -1
  75. package/lib/commonjs/store/apis/softDeleteMessage.js +45 -0
  76. package/lib/commonjs/store/apis/softDeleteMessage.js.map +1 -0
  77. package/lib/commonjs/store/apis/updateMessage.js +7 -11
  78. package/lib/commonjs/store/apis/updateMessage.js.map +1 -1
  79. package/lib/commonjs/store/apis/updateReaction.js +11 -9
  80. package/lib/commonjs/store/apis/updateReaction.js.map +1 -1
  81. package/lib/commonjs/store/apis/upsertAppSettings.js +9 -7
  82. package/lib/commonjs/store/apis/upsertAppSettings.js.map +1 -1
  83. package/lib/commonjs/store/apis/upsertChannelData.js +4 -4
  84. package/lib/commonjs/store/apis/upsertChannelData.js.map +1 -1
  85. package/lib/commonjs/store/apis/upsertChannelDataFromChannel.js +3 -3
  86. package/lib/commonjs/store/apis/upsertChannelDataFromChannel.js.map +1 -1
  87. package/lib/commonjs/store/apis/upsertChannels.js +86 -83
  88. package/lib/commonjs/store/apis/upsertChannels.js.map +1 -1
  89. package/lib/commonjs/store/apis/upsertCidsForQuery.js +4 -4
  90. package/lib/commonjs/store/apis/upsertCidsForQuery.js.map +1 -1
  91. package/lib/commonjs/store/apis/upsertMembers.js +4 -4
  92. package/lib/commonjs/store/apis/upsertMembers.js.map +1 -1
  93. package/lib/commonjs/store/apis/upsertMessages.js +4 -4
  94. package/lib/commonjs/store/apis/upsertMessages.js.map +1 -1
  95. package/lib/commonjs/store/apis/upsertPoll.js +42 -0
  96. package/lib/commonjs/store/apis/upsertPoll.js.map +1 -0
  97. package/lib/commonjs/store/apis/upsertReads.js +4 -4
  98. package/lib/commonjs/store/apis/upsertReads.js.map +1 -1
  99. package/lib/commonjs/store/apis/upsertUserSyncStatus.js +13 -7
  100. package/lib/commonjs/store/apis/upsertUserSyncStatus.js.map +1 -1
  101. package/lib/commonjs/store/mappers/mapMemberToStorable.js +6 -2
  102. package/lib/commonjs/store/mappers/mapMemberToStorable.js.map +1 -1
  103. package/lib/commonjs/store/mappers/mapReadToStorable.js +3 -1
  104. package/lib/commonjs/store/mappers/mapReadToStorable.js.map +1 -1
  105. package/lib/commonjs/store/mappers/mapStorableToMember.js +5 -1
  106. package/lib/commonjs/store/mappers/mapStorableToMember.js.map +1 -1
  107. package/lib/commonjs/store/mappers/mapStorableToRead.js +3 -1
  108. package/lib/commonjs/store/mappers/mapStorableToRead.js.map +1 -1
  109. package/lib/commonjs/store/mappers/mapStorableToTask.js.map +1 -1
  110. package/lib/commonjs/store/schema.js +2 -0
  111. package/lib/commonjs/store/schema.js.map +1 -1
  112. package/lib/commonjs/utils/addReactionToLocalState.js +62 -81
  113. package/lib/commonjs/utils/addReactionToLocalState.js.map +1 -1
  114. package/lib/commonjs/utils/removeReactionFromLocalState.js +0 -6
  115. package/lib/commonjs/utils/removeReactionFromLocalState.js.map +1 -1
  116. package/lib/commonjs/version.json +1 -1
  117. package/lib/module/components/Channel/Channel.js +214 -237
  118. package/lib/module/components/Channel/Channel.js.map +1 -1
  119. package/lib/module/components/ChannelList/ChannelList.js +2 -19
  120. package/lib/module/components/ChannelList/ChannelList.js.map +1 -1
  121. package/lib/module/components/ChannelList/hooks/usePaginatedChannels.js +48 -176
  122. package/lib/module/components/ChannelList/hooks/usePaginatedChannels.js.map +1 -1
  123. package/lib/module/components/ChannelPreview/hooks/useIsChannelMuted.js +13 -2
  124. package/lib/module/components/ChannelPreview/hooks/useIsChannelMuted.js.map +1 -1
  125. package/lib/module/components/Chat/Chat.js +33 -46
  126. package/lib/module/components/Chat/Chat.js.map +1 -1
  127. package/lib/module/components/Chat/hooks/useAppSettings.js +47 -120
  128. package/lib/module/components/Chat/hooks/useAppSettings.js.map +1 -1
  129. package/lib/module/components/Chat/hooks/useSyncDatabase.js +0 -15
  130. package/lib/module/components/Chat/hooks/useSyncDatabase.js.map +1 -1
  131. package/lib/module/components/MessageMenu/MessageUserReactions.js +5 -0
  132. package/lib/module/components/MessageMenu/MessageUserReactions.js.map +1 -1
  133. package/lib/module/components/MessageMenu/hooks/useFetchReactions.js +92 -108
  134. package/lib/module/components/MessageMenu/hooks/useFetchReactions.js.map +1 -1
  135. package/lib/module/index.js +8 -0
  136. package/lib/module/index.js.map +1 -1
  137. package/lib/module/mock-builders/event/channelVisible.js +13 -0
  138. package/lib/module/mock-builders/event/channelVisible.js.map +1 -0
  139. package/lib/module/mock-builders/event/memberAdded.js +4 -2
  140. package/lib/module/mock-builders/event/memberAdded.js.map +1 -1
  141. package/lib/module/mock-builders/event/memberRemoved.js +2 -1
  142. package/lib/module/mock-builders/event/memberRemoved.js.map +1 -1
  143. package/lib/module/mock-builders/event/memberUpdated.js +2 -1
  144. package/lib/module/mock-builders/event/memberUpdated.js.map +1 -1
  145. package/lib/module/mock-builders/event/messageNew.js +4 -2
  146. package/lib/module/mock-builders/event/messageNew.js.map +1 -1
  147. package/lib/module/mock-builders/event/messageRead.js +6 -3
  148. package/lib/module/mock-builders/event/messageRead.js.map +1 -1
  149. package/lib/module/mock-builders/event/notificationMarkUnread.js +3 -0
  150. package/lib/module/mock-builders/event/notificationMarkUnread.js.map +1 -1
  151. package/lib/module/mock-builders/generator/channel.js +10 -3
  152. package/lib/module/mock-builders/generator/channel.js.map +1 -1
  153. package/lib/module/store/OfflineDB.js +108 -0
  154. package/lib/module/store/OfflineDB.js.map +1 -0
  155. package/lib/module/store/SqliteClient.js +11 -10
  156. package/lib/module/store/SqliteClient.js.map +1 -1
  157. package/lib/module/store/apis/addPendingTask.js.map +1 -1
  158. package/lib/module/store/apis/channelExists.js +34 -0
  159. package/lib/module/store/apis/channelExists.js.map +1 -0
  160. package/lib/module/store/apis/deleteChannel.js +4 -4
  161. package/lib/module/store/apis/deleteChannel.js.map +1 -1
  162. package/lib/module/store/apis/deleteMember.js +4 -4
  163. package/lib/module/store/apis/deleteMember.js.map +1 -1
  164. package/lib/module/store/apis/deleteMessage.js +16 -12
  165. package/lib/module/store/apis/deleteMessage.js.map +1 -1
  166. package/lib/module/store/apis/deleteMessagesForChannel.js +11 -12
  167. package/lib/module/store/apis/deleteMessagesForChannel.js.map +1 -1
  168. package/lib/module/store/apis/deleteReaction.js +29 -17
  169. package/lib/module/store/apis/deleteReaction.js.map +1 -1
  170. package/lib/module/store/apis/deleteReactions.js +4 -4
  171. package/lib/module/store/apis/deleteReactions.js.map +1 -1
  172. package/lib/module/store/apis/dropPendingTasks.js +41 -0
  173. package/lib/module/store/apis/dropPendingTasks.js.map +1 -0
  174. package/lib/module/store/apis/getChannelMessages.js +1 -1
  175. package/lib/module/store/apis/getChannelMessages.js.map +1 -1
  176. package/lib/module/store/apis/getChannels.js +18 -21
  177. package/lib/module/store/apis/getChannels.js.map +1 -1
  178. package/lib/module/store/apis/getLastSyncedAt.js +3 -9
  179. package/lib/module/store/apis/getLastSyncedAt.js.map +1 -1
  180. package/lib/module/store/apis/getReactionsforFilterSort.js +5 -8
  181. package/lib/module/store/apis/getReactionsforFilterSort.js.map +1 -1
  182. package/lib/module/store/apis/index.js +103 -4
  183. package/lib/module/store/apis/index.js.map +1 -1
  184. package/lib/module/store/apis/insertReaction.js +4 -4
  185. package/lib/module/store/apis/insertReaction.js.map +1 -1
  186. package/lib/module/store/apis/queries/selectMessagesForChannels.js +1 -1
  187. package/lib/module/store/apis/queries/selectMessagesForChannels.js.map +1 -1
  188. package/lib/module/store/apis/queries/selectReactionsForMessages.js +25 -5
  189. package/lib/module/store/apis/queries/selectReactionsForMessages.js.map +1 -1
  190. package/lib/module/store/apis/softDeleteMessage.js +45 -0
  191. package/lib/module/store/apis/softDeleteMessage.js.map +1 -0
  192. package/lib/module/store/apis/updateMessage.js +7 -11
  193. package/lib/module/store/apis/updateMessage.js.map +1 -1
  194. package/lib/module/store/apis/updateReaction.js +11 -9
  195. package/lib/module/store/apis/updateReaction.js.map +1 -1
  196. package/lib/module/store/apis/upsertAppSettings.js +9 -7
  197. package/lib/module/store/apis/upsertAppSettings.js.map +1 -1
  198. package/lib/module/store/apis/upsertChannelData.js +4 -4
  199. package/lib/module/store/apis/upsertChannelData.js.map +1 -1
  200. package/lib/module/store/apis/upsertChannelDataFromChannel.js +3 -3
  201. package/lib/module/store/apis/upsertChannelDataFromChannel.js.map +1 -1
  202. package/lib/module/store/apis/upsertChannels.js +86 -83
  203. package/lib/module/store/apis/upsertChannels.js.map +1 -1
  204. package/lib/module/store/apis/upsertCidsForQuery.js +4 -4
  205. package/lib/module/store/apis/upsertCidsForQuery.js.map +1 -1
  206. package/lib/module/store/apis/upsertMembers.js +4 -4
  207. package/lib/module/store/apis/upsertMembers.js.map +1 -1
  208. package/lib/module/store/apis/upsertMessages.js +4 -4
  209. package/lib/module/store/apis/upsertMessages.js.map +1 -1
  210. package/lib/module/store/apis/upsertPoll.js +42 -0
  211. package/lib/module/store/apis/upsertPoll.js.map +1 -0
  212. package/lib/module/store/apis/upsertReads.js +4 -4
  213. package/lib/module/store/apis/upsertReads.js.map +1 -1
  214. package/lib/module/store/apis/upsertUserSyncStatus.js +13 -7
  215. package/lib/module/store/apis/upsertUserSyncStatus.js.map +1 -1
  216. package/lib/module/store/mappers/mapMemberToStorable.js +6 -2
  217. package/lib/module/store/mappers/mapMemberToStorable.js.map +1 -1
  218. package/lib/module/store/mappers/mapReadToStorable.js +3 -1
  219. package/lib/module/store/mappers/mapReadToStorable.js.map +1 -1
  220. package/lib/module/store/mappers/mapStorableToMember.js +5 -1
  221. package/lib/module/store/mappers/mapStorableToMember.js.map +1 -1
  222. package/lib/module/store/mappers/mapStorableToRead.js +3 -1
  223. package/lib/module/store/mappers/mapStorableToRead.js.map +1 -1
  224. package/lib/module/store/mappers/mapStorableToTask.js.map +1 -1
  225. package/lib/module/store/schema.js +2 -0
  226. package/lib/module/store/schema.js.map +1 -1
  227. package/lib/module/utils/addReactionToLocalState.js +62 -81
  228. package/lib/module/utils/addReactionToLocalState.js.map +1 -1
  229. package/lib/module/utils/removeReactionFromLocalState.js +0 -6
  230. package/lib/module/utils/removeReactionFromLocalState.js.map +1 -1
  231. package/lib/module/version.json +1 -1
  232. package/lib/typescript/components/Channel/Channel.d.ts +1 -1
  233. package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
  234. package/lib/typescript/components/Channel/hooks/useCreateThreadContext.d.ts.map +1 -1
  235. package/lib/typescript/components/ChannelList/ChannelList.d.ts.map +1 -1
  236. package/lib/typescript/components/ChannelList/hooks/usePaginatedChannels.d.ts +1 -1
  237. package/lib/typescript/components/ChannelList/hooks/usePaginatedChannels.d.ts.map +1 -1
  238. package/lib/typescript/components/ChannelPreview/hooks/useIsChannelMuted.d.ts.map +1 -1
  239. package/lib/typescript/components/Chat/Chat.d.ts.map +1 -1
  240. package/lib/typescript/components/Chat/hooks/useAppSettings.d.ts.map +1 -1
  241. package/lib/typescript/components/Chat/hooks/useSyncDatabase.d.ts +9 -1
  242. package/lib/typescript/components/Chat/hooks/useSyncDatabase.d.ts.map +1 -1
  243. package/lib/typescript/components/MessageMenu/MessageUserReactions.d.ts.map +1 -1
  244. package/lib/typescript/components/MessageMenu/hooks/useFetchReactions.d.ts.map +1 -1
  245. package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts +1 -1
  246. package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts.map +1 -1
  247. package/lib/typescript/hooks/useTranslatedMessage.d.ts.map +1 -1
  248. package/lib/typescript/index.d.ts +1 -0
  249. package/lib/typescript/index.d.ts.map +1 -1
  250. package/lib/typescript/store/OfflineDB.d.ts +112 -0
  251. package/lib/typescript/store/OfflineDB.d.ts.map +1 -0
  252. package/lib/typescript/store/SqliteClient.d.ts +1 -1
  253. package/lib/typescript/store/SqliteClient.d.ts.map +1 -1
  254. package/lib/typescript/store/apis/addPendingTask.d.ts +1 -1
  255. package/lib/typescript/store/apis/addPendingTask.d.ts.map +1 -1
  256. package/lib/typescript/store/apis/channelExists.d.ts +4 -0
  257. package/lib/typescript/store/apis/channelExists.d.ts.map +1 -0
  258. package/lib/typescript/store/apis/deleteChannel.d.ts +2 -2
  259. package/lib/typescript/store/apis/deleteChannel.d.ts.map +1 -1
  260. package/lib/typescript/store/apis/deleteMember.d.ts +2 -2
  261. package/lib/typescript/store/apis/deleteMember.d.ts.map +1 -1
  262. package/lib/typescript/store/apis/deleteMessage.d.ts +3 -3
  263. package/lib/typescript/store/apis/deleteMessage.d.ts.map +1 -1
  264. package/lib/typescript/store/apis/deleteMessagesForChannel.d.ts +4 -3
  265. package/lib/typescript/store/apis/deleteMessagesForChannel.d.ts.map +1 -1
  266. package/lib/typescript/store/apis/deleteReaction.d.ts +7 -6
  267. package/lib/typescript/store/apis/deleteReaction.d.ts.map +1 -1
  268. package/lib/typescript/store/apis/deleteReactions.d.ts +2 -2
  269. package/lib/typescript/store/apis/deleteReactions.d.ts.map +1 -1
  270. package/lib/typescript/store/apis/dropPendingTasks.d.ts +16 -0
  271. package/lib/typescript/store/apis/dropPendingTasks.d.ts.map +1 -0
  272. package/lib/typescript/store/apis/getChannels.d.ts.map +1 -1
  273. package/lib/typescript/store/apis/getLastSyncedAt.d.ts +1 -1
  274. package/lib/typescript/store/apis/getLastSyncedAt.d.ts.map +1 -1
  275. package/lib/typescript/store/apis/getPendingTasks.d.ts +1 -1
  276. package/lib/typescript/store/apis/getPendingTasks.d.ts.map +1 -1
  277. package/lib/typescript/store/apis/getReactionsforFilterSort.d.ts +5 -3
  278. package/lib/typescript/store/apis/getReactionsforFilterSort.d.ts.map +1 -1
  279. package/lib/typescript/store/apis/index.d.ts +10 -1
  280. package/lib/typescript/store/apis/index.d.ts.map +1 -1
  281. package/lib/typescript/store/apis/insertReaction.d.ts +2 -2
  282. package/lib/typescript/store/apis/insertReaction.d.ts.map +1 -1
  283. package/lib/typescript/store/apis/queries/selectReactionsForMessages.d.ts +5 -1
  284. package/lib/typescript/store/apis/queries/selectReactionsForMessages.d.ts.map +1 -1
  285. package/lib/typescript/store/apis/softDeleteMessage.d.ts +5 -0
  286. package/lib/typescript/store/apis/softDeleteMessage.d.ts.map +1 -0
  287. package/lib/typescript/store/apis/updateMessage.d.ts +2 -2
  288. package/lib/typescript/store/apis/updateMessage.d.ts.map +1 -1
  289. package/lib/typescript/store/apis/updateReaction.d.ts +2 -2
  290. package/lib/typescript/store/apis/updateReaction.d.ts.map +1 -1
  291. package/lib/typescript/store/apis/upsertAppSettings.d.ts +3 -3
  292. package/lib/typescript/store/apis/upsertAppSettings.d.ts.map +1 -1
  293. package/lib/typescript/store/apis/upsertChannelData.d.ts +2 -2
  294. package/lib/typescript/store/apis/upsertChannelData.d.ts.map +1 -1
  295. package/lib/typescript/store/apis/upsertChannelDataFromChannel.d.ts +2 -2
  296. package/lib/typescript/store/apis/upsertChannelDataFromChannel.d.ts.map +1 -1
  297. package/lib/typescript/store/apis/upsertChannels.d.ts +3 -5
  298. package/lib/typescript/store/apis/upsertChannels.d.ts.map +1 -1
  299. package/lib/typescript/store/apis/upsertCidsForQuery.d.ts +2 -2
  300. package/lib/typescript/store/apis/upsertCidsForQuery.d.ts.map +1 -1
  301. package/lib/typescript/store/apis/upsertMembers.d.ts +2 -2
  302. package/lib/typescript/store/apis/upsertMembers.d.ts.map +1 -1
  303. package/lib/typescript/store/apis/upsertMessages.d.ts +2 -2
  304. package/lib/typescript/store/apis/upsertMessages.d.ts.map +1 -1
  305. package/lib/typescript/store/apis/upsertPoll.d.ts +7 -0
  306. package/lib/typescript/store/apis/upsertPoll.d.ts.map +1 -0
  307. package/lib/typescript/store/apis/upsertReads.d.ts +2 -2
  308. package/lib/typescript/store/apis/upsertReads.d.ts.map +1 -1
  309. package/lib/typescript/store/apis/upsertUserSyncStatus.d.ts +3 -2
  310. package/lib/typescript/store/apis/upsertUserSyncStatus.d.ts.map +1 -1
  311. package/lib/typescript/store/mappers/mapMemberToStorable.d.ts.map +1 -1
  312. package/lib/typescript/store/mappers/mapReadToStorable.d.ts.map +1 -1
  313. package/lib/typescript/store/mappers/mapStorableToMember.d.ts.map +1 -1
  314. package/lib/typescript/store/mappers/mapStorableToRead.d.ts.map +1 -1
  315. package/lib/typescript/store/mappers/mapStorableToTask.d.ts +2 -1
  316. package/lib/typescript/store/mappers/mapStorableToTask.d.ts.map +1 -1
  317. package/lib/typescript/store/mappers/mapTaskToStorable.d.ts +18 -10
  318. package/lib/typescript/store/mappers/mapTaskToStorable.d.ts.map +1 -1
  319. package/lib/typescript/store/schema.d.ts +3 -2
  320. package/lib/typescript/store/schema.d.ts.map +1 -1
  321. package/lib/typescript/utils/addReactionToLocalState.d.ts +1 -1
  322. package/lib/typescript/utils/addReactionToLocalState.d.ts.map +1 -1
  323. package/lib/typescript/utils/removeReactionFromLocalState.d.ts.map +1 -1
  324. package/package.json +5 -4
  325. package/src/__tests__/offline-support/offline-feature.js +894 -21
  326. package/src/__tests__/offline-support/optimistic-update.js +154 -44
  327. package/src/components/Channel/Channel.tsx +95 -118
  328. package/src/components/ChannelList/ChannelList.tsx +1 -23
  329. package/src/components/ChannelList/hooks/usePaginatedChannels.ts +9 -93
  330. package/src/components/ChannelPreview/hooks/useIsChannelMuted.ts +20 -2
  331. package/src/components/Chat/Chat.tsx +21 -45
  332. package/src/components/Chat/__tests__/Chat.test.js +21 -13
  333. package/src/components/Chat/hooks/useAppSettings.ts +17 -54
  334. package/src/components/Chat/hooks/useSyncDatabase.ts +11 -17
  335. package/src/components/MessageMenu/MessageUserReactions.tsx +7 -1
  336. package/src/components/MessageMenu/hooks/useFetchReactions.ts +68 -35
  337. package/src/contexts/messagesContext/MessagesContext.tsx +1 -1
  338. package/src/index.ts +1 -0
  339. package/src/mock-builders/event/channelVisible.js +7 -0
  340. package/src/mock-builders/event/memberAdded.js +3 -1
  341. package/src/mock-builders/event/memberRemoved.js +1 -0
  342. package/src/mock-builders/event/memberUpdated.js +1 -0
  343. package/src/mock-builders/event/messageNew.js +1 -0
  344. package/src/mock-builders/event/messageRead.js +5 -2
  345. package/src/mock-builders/event/notificationMarkUnread.js +3 -0
  346. package/src/mock-builders/generator/channel.ts +15 -3
  347. package/src/store/OfflineDB.ts +93 -0
  348. package/src/store/SqliteClient.ts +7 -1
  349. package/src/store/apis/addPendingTask.ts +2 -1
  350. package/src/store/apis/channelExists.ts +14 -0
  351. package/src/store/apis/deleteChannel.ts +9 -3
  352. package/src/store/apis/deleteMember.ts +4 -4
  353. package/src/store/apis/deleteMessage.ts +18 -8
  354. package/src/store/apis/deleteMessagesForChannel.ts +12 -8
  355. package/src/store/apis/deleteReaction.ts +39 -19
  356. package/src/store/apis/deleteReactions.ts +4 -4
  357. package/src/store/apis/dropPendingTasks.ts +32 -0
  358. package/src/store/apis/getChannelMessages.ts +1 -1
  359. package/src/store/apis/getChannels.ts +10 -7
  360. package/src/store/apis/getLastSyncedAt.ts +2 -5
  361. package/src/store/apis/getReactionsforFilterSort.ts +8 -7
  362. package/src/store/apis/index.ts +10 -1
  363. package/src/store/apis/insertReaction.ts +4 -4
  364. package/src/store/apis/queries/selectMessagesForChannels.ts +2 -2
  365. package/src/store/apis/queries/selectReactionsForMessages.ts +21 -2
  366. package/src/store/apis/softDeleteMessage.ts +32 -0
  367. package/src/store/apis/updateMessage.ts +3 -10
  368. package/src/store/apis/updateReaction.ts +7 -5
  369. package/src/store/apis/upsertAppSettings.ts +13 -9
  370. package/src/store/apis/upsertChannelData.ts +4 -4
  371. package/src/store/apis/upsertChannelDataFromChannel.ts +3 -3
  372. package/src/store/apis/upsertChannels.ts +14 -24
  373. package/src/store/apis/upsertCidsForQuery.ts +4 -4
  374. package/src/store/apis/upsertMembers.ts +4 -4
  375. package/src/store/apis/upsertMessages.ts +4 -4
  376. package/src/store/apis/upsertPoll.ts +29 -0
  377. package/src/store/apis/upsertReads.ts +4 -4
  378. package/src/store/apis/upsertUserSyncStatus.ts +13 -5
  379. package/src/store/mappers/mapMemberToStorable.ts +4 -0
  380. package/src/store/mappers/mapReadToStorable.ts +2 -1
  381. package/src/store/mappers/mapStorableToMember.ts +4 -0
  382. package/src/store/mappers/mapStorableToRead.ts +2 -1
  383. package/src/store/mappers/mapStorableToTask.ts +3 -1
  384. package/src/store/mappers/mapTaskToStorable.ts +1 -1
  385. package/src/store/schema.ts +5 -3
  386. package/src/utils/addReactionToLocalState.ts +9 -63
  387. package/src/utils/removeReactionFromLocalState.ts +0 -8
  388. package/src/version.json +1 -1
  389. package/lib/commonjs/components/Chat/hooks/handleEventToSyncDB.js +0 -406
  390. package/lib/commonjs/components/Chat/hooks/handleEventToSyncDB.js.map +0 -1
  391. package/lib/commonjs/store/apis/updatePollMessage.js +0 -84
  392. package/lib/commonjs/store/apis/updatePollMessage.js.map +0 -1
  393. package/lib/commonjs/utils/DBSyncManager.js +0 -470
  394. package/lib/commonjs/utils/DBSyncManager.js.map +0 -1
  395. package/lib/module/components/Chat/hooks/handleEventToSyncDB.js +0 -406
  396. package/lib/module/components/Chat/hooks/handleEventToSyncDB.js.map +0 -1
  397. package/lib/module/store/apis/updatePollMessage.js +0 -84
  398. package/lib/module/store/apis/updatePollMessage.js.map +0 -1
  399. package/lib/module/utils/DBSyncManager.js +0 -470
  400. package/lib/module/utils/DBSyncManager.js.map +0 -1
  401. package/lib/typescript/components/Chat/hooks/handleEventToSyncDB.d.ts +0 -4
  402. package/lib/typescript/components/Chat/hooks/handleEventToSyncDB.d.ts.map +0 -1
  403. package/lib/typescript/store/apis/updatePollMessage.d.ts +0 -10
  404. package/lib/typescript/store/apis/updatePollMessage.d.ts.map +0 -1
  405. package/lib/typescript/utils/DBSyncManager.d.ts +0 -50
  406. package/lib/typescript/utils/DBSyncManager.d.ts.map +0 -1
  407. package/src/components/Chat/hooks/handleEventToSyncDB.ts +0 -290
  408. package/src/store/apis/updatePollMessage.ts +0 -71
  409. package/src/utils/DBSyncManager.ts +0 -242
@@ -13,8 +13,11 @@ import { useChannelsContext } from '../../contexts/channelsContext/ChannelsConte
13
13
  import { getOrCreateChannelApi } from '../../mock-builders/api/getOrCreateChannel';
14
14
  import { queryChannelsApi } from '../../mock-builders/api/queryChannels';
15
15
  import { useMockedApis } from '../../mock-builders/api/useMockedApis';
16
+ import dispatchChannelDeletedEvent from '../../mock-builders/event/channelDeleted';
17
+ import dispatchChannelHiddenEvent from '../../mock-builders/event/channelHidden';
16
18
  import dispatchChannelTruncatedEvent from '../../mock-builders/event/channelTruncated';
17
19
  import dispatchChannelUpdatedEvent from '../../mock-builders/event/channelUpdated';
20
+ import dispatchChannelVisibleEvent from '../../mock-builders/event/channelVisible';
18
21
  import dispatchConnectionChangedEvent from '../../mock-builders/event/connectionChanged';
19
22
  import dispatchMemberAddedEvent from '../../mock-builders/event/memberAdded';
20
23
  import dispatchMemberRemovedEvent from '../../mock-builders/event/memberRemoved';
@@ -23,6 +26,7 @@ import dispatchMessageNewEvent from '../../mock-builders/event/messageNew';
23
26
  import dispatchMessageReadEvent from '../../mock-builders/event/messageRead';
24
27
  import dispatchMessageUpdatedEvent from '../../mock-builders/event/messageUpdated';
25
28
  import dispatchNotificationAddedToChannel from '../../mock-builders/event/notificationAddedToChannel';
29
+ import dispatchNotificationMarkUnread from '../../mock-builders/event/notificationMarkUnread';
26
30
  import dispatchNotificationMessageNewEvent from '../../mock-builders/event/notificationMessageNew';
27
31
  import dispatchNotificationRemovedFromChannel from '../../mock-builders/event/notificationRemovedFromChannel';
28
32
  import dispatchReactionDeletedEvent from '../../mock-builders/event/reactionDeleted';
@@ -124,8 +128,10 @@ export const Generic = () => {
124
128
  const createChannel = (messagesOverride) => {
125
129
  const id = uuidv4();
126
130
  const cid = `messaging:${id}`;
127
- const begin = getRandomInt(0, allUsers.length - 2); // begin shouldn't be the end of users.length
128
- const end = getRandomInt(begin + 1, allUsers.length - 1);
131
+ // always guarantee at least 2 members for ease of use; cases that need to test specific behaviour
132
+ // for 1 or 0 member channels should explicitly generate them.
133
+ const begin = getRandomInt(0, allUsers.length - 3); // begin shouldn't be the end of users.length
134
+ const end = getRandomInt(begin + 2, allUsers.length - 1);
129
135
  const usersForMembers = allUsers.slice(begin, end);
130
136
  const members = usersForMembers.map((user) =>
131
137
  generateMember({
@@ -133,6 +139,8 @@ export const Generic = () => {
133
139
  user,
134
140
  }),
135
141
  );
142
+ members.push(generateMember({ cid, user: chatClient.user }));
143
+
136
144
  const messages =
137
145
  messagesOverride ||
138
146
  Array(10)
@@ -162,8 +170,9 @@ export const Generic = () => {
162
170
  });
163
171
 
164
172
  const reads = members.map((member) => ({
173
+ cid,
165
174
  last_read: new Date(new Date().setDate(new Date().getDate() - getRandomInt(0, 20))),
166
- unread_messages: getRandomInt(0, messages.length),
175
+ unread_messages: 0,
167
176
  user: member.user,
168
177
  }));
169
178
 
@@ -176,21 +185,23 @@ export const Generic = () => {
176
185
  id,
177
186
  members,
178
187
  messages,
188
+ read: reads,
179
189
  });
180
190
  };
181
191
 
182
192
  beforeEach(async () => {
183
193
  jest.clearAllMocks();
194
+ chatClient = await getTestClientWithUser({ id: 'dan' });
184
195
  allUsers = Array(20).fill(1).map(generateUser);
196
+ allUsers.push(chatClient.user);
185
197
  allMessages = [];
186
198
  allMembers = [];
187
199
  allReactions = [];
188
200
  allReads = [];
201
+
189
202
  channels = Array(10)
190
203
  .fill(1)
191
204
  .map(() => createChannel());
192
-
193
- chatClient = await getTestClientWithUser({ id: 'dan' });
194
205
  await BetterSqlite.openDB();
195
206
  BetterSqlite.dropAllTables();
196
207
  });
@@ -282,12 +293,7 @@ export const Generic = () => {
282
293
  );
283
294
  readsRows.forEach((row) =>
284
295
  expect(
285
- allReads.filter(
286
- (r) =>
287
- r.last_read === row.lastRead &&
288
- r.user.id === row.userId &&
289
- r.unread_messages === row.unreadMessages,
290
- ),
296
+ allReads.filter((r) => r.user.id === row.userId && r.cid === row.cid),
291
297
  ).toHaveLength(1),
292
298
  );
293
299
  });
@@ -316,6 +322,7 @@ export const Generic = () => {
316
322
  await act(() => dispatchConnectionChangedEvent(chatClient, false));
317
323
  // await waiter();
318
324
  await act(() => dispatchConnectionChangedEvent(chatClient));
325
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
319
326
 
320
327
  await waitFor(async () => {
321
328
  expect(screen.getByTestId('channel-list')).toBeTruthy();
@@ -328,8 +335,10 @@ export const Generic = () => {
328
335
 
329
336
  renderComponent();
330
337
 
338
+ act(() => dispatchConnectionChangedEvent(chatClient));
339
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
340
+
331
341
  await waitFor(async () => {
332
- act(() => dispatchConnectionChangedEvent(chatClient));
333
342
  expect(screen.getByTestId('channel-list')).toBeTruthy();
334
343
  await expectAllChannelsWithStateToBeInDB(screen.queryAllByLabelText);
335
344
  });
@@ -342,8 +351,11 @@ export const Generic = () => {
342
351
 
343
352
  renderComponent();
344
353
 
345
- await waitFor(() => {
354
+ await waitFor(async () => {
346
355
  act(() => dispatchConnectionChangedEvent(chatClient));
356
+ await act(
357
+ async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true),
358
+ );
347
359
  expect(screen.getByTestId('channel-list')).toBeTruthy();
348
360
  expect(screen.getByTestId(emptyChannel.cid)).toBeTruthy();
349
361
  expect(chatClient.hydrateActiveChannels).toHaveBeenCalled();
@@ -356,18 +368,151 @@ export const Generic = () => {
356
368
 
357
369
  renderComponent();
358
370
  act(() => dispatchConnectionChangedEvent(chatClient));
371
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
359
372
  await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
373
+ const targetChannel = channels[0].channel;
360
374
  const newMessage = generateMessage({
361
- cid: channels[0].channel.cid,
375
+ cid: targetChannel.cid,
362
376
  user: generateUser(),
363
377
  });
364
- act(() => dispatchMessageNewEvent(chatClient, newMessage, channels[0].channel));
378
+ act(() => dispatchMessageNewEvent(chatClient, newMessage, targetChannel));
365
379
 
366
380
  await waitFor(async () => {
367
381
  const messagesRows = await BetterSqlite.selectFromTable('messages');
368
- const matchingRows = messagesRows.filter((m) => m.id === newMessage.id);
369
- expect(matchingRows.length).toBe(1);
370
- expect(matchingRows[0].id).toBe(newMessage.id);
382
+ const readRows = await BetterSqlite.selectFromTable('reads');
383
+ const matchingMessageRows = messagesRows.filter((m) => m.id === newMessage.id);
384
+ const matchingReadRows = readRows.filter(
385
+ (r) => targetChannel.cid === r.cid && chatClient.userID === r.userId,
386
+ );
387
+
388
+ expect(matchingMessageRows.length).toBe(1);
389
+ expect(matchingMessageRows[0].id).toBe(newMessage.id);
390
+ expect(matchingReadRows.length).toBe(1);
391
+ expect(matchingReadRows[0].unreadMessages).toBe(1);
392
+ });
393
+ });
394
+
395
+ it('should correctly handle multiple new messages and add them to the database', async () => {
396
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
397
+
398
+ renderComponent();
399
+ act(() => dispatchConnectionChangedEvent(chatClient));
400
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
401
+ await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
402
+ const targetChannel = channels[0].channel;
403
+
404
+ // check if the reads state is correct first
405
+ await waitFor(async () => {
406
+ const readRows = await BetterSqlite.selectFromTable('reads');
407
+ const matchingReadRows = readRows.filter(
408
+ (r) => targetChannel.cid === r.cid && chatClient.userID === r.userId,
409
+ );
410
+
411
+ expect(matchingReadRows.length).toBe(1);
412
+ expect(matchingReadRows[0].unreadMessages).toBe(0);
413
+ });
414
+
415
+ const newMessages = [
416
+ generateMessage({
417
+ cid: targetChannel.cid,
418
+ user: generateUser(),
419
+ }),
420
+ generateMessage({
421
+ cid: targetChannel.cid,
422
+ user: generateUser(),
423
+ }),
424
+ generateMessage({
425
+ cid: targetChannel.cid,
426
+ user: generateUser(),
427
+ }),
428
+ ];
429
+
430
+ newMessages.forEach((newMessage) => {
431
+ act(() => dispatchMessageNewEvent(chatClient, newMessage, targetChannel));
432
+ });
433
+
434
+ await waitFor(async () => {
435
+ const messagesRows = await BetterSqlite.selectFromTable('messages');
436
+ const readRows = await BetterSqlite.selectFromTable('reads');
437
+ const matchingMessageRows = messagesRows.filter((m) =>
438
+ newMessages.some((newMessage) => newMessage.id === m.id),
439
+ );
440
+ const matchingReadRows = readRows.filter(
441
+ (r) => targetChannel.cid === r.cid && chatClient.userID === r.userId,
442
+ );
443
+
444
+ expect(matchingMessageRows.length).toBe(3);
445
+ newMessages.forEach((newMessage) => {
446
+ expect(
447
+ matchingMessageRows.some(
448
+ (matchingMessageRow) => matchingMessageRow.id === newMessage.id,
449
+ ),
450
+ ).toBe(true);
451
+ });
452
+ expect(matchingReadRows.length).toBe(1);
453
+ expect(matchingReadRows[0].unreadMessages).toBe(3);
454
+ });
455
+ });
456
+
457
+ it('should correctly handle multiple new messages from our own user', async () => {
458
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
459
+
460
+ renderComponent();
461
+ act(() => dispatchConnectionChangedEvent(chatClient));
462
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
463
+ await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
464
+ const targetChannel = channels[0].channel;
465
+
466
+ // check if the reads state is correct first
467
+ await waitFor(async () => {
468
+ const readRows = await BetterSqlite.selectFromTable('reads');
469
+ const matchingReadRows = readRows.filter(
470
+ (r) => targetChannel.cid === r.cid && chatClient.userID === r.userId,
471
+ );
472
+
473
+ expect(matchingReadRows.length).toBe(1);
474
+ expect(matchingReadRows[0].unreadMessages).toBe(0);
475
+ });
476
+
477
+ const newMessages = [
478
+ generateMessage({
479
+ cid: targetChannel.cid,
480
+ user: chatClient.user,
481
+ }),
482
+ generateMessage({
483
+ cid: targetChannel.cid,
484
+ user: chatClient.user,
485
+ }),
486
+ generateMessage({
487
+ cid: targetChannel.cid,
488
+ user: chatClient.user,
489
+ }),
490
+ ];
491
+
492
+ newMessages.forEach((newMessage) => {
493
+ act(() => dispatchMessageNewEvent(chatClient, newMessage, targetChannel));
494
+ });
495
+
496
+ await waitFor(async () => {
497
+ const messagesRows = await BetterSqlite.selectFromTable('messages');
498
+ const readRows = await BetterSqlite.selectFromTable('reads');
499
+ const matchingMessageRows = messagesRows.filter((m) =>
500
+ newMessages.some((newMessage) => newMessage.id === m.id),
501
+ );
502
+ const matchingReadRows = readRows.filter(
503
+ (r) => targetChannel.cid === r.cid && chatClient.userID === r.userId,
504
+ );
505
+
506
+ expect(matchingMessageRows.length).toBe(3);
507
+ newMessages.forEach((newMessage) => {
508
+ expect(
509
+ matchingMessageRows.some(
510
+ (matchingMessageRow) => matchingMessageRow.id === newMessage.id,
511
+ ),
512
+ ).toBe(true);
513
+ });
514
+ expect(matchingReadRows.length).toBe(1);
515
+ expect(matchingReadRows[0].unreadMessages).toBe(0);
371
516
  });
372
517
  });
373
518
 
@@ -375,8 +520,11 @@ export const Generic = () => {
375
520
  useMockedApis(chatClient, [queryChannelsApi(channels)]);
376
521
 
377
522
  renderComponent();
378
- await waitFor(() => {
523
+ await waitFor(async () => {
379
524
  act(() => dispatchConnectionChangedEvent(chatClient));
525
+ await act(
526
+ async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true),
527
+ );
380
528
  expect(screen.getByTestId('channel-list')).toBeTruthy();
381
529
  });
382
530
 
@@ -399,6 +547,7 @@ export const Generic = () => {
399
547
 
400
548
  renderComponent();
401
549
  act(() => dispatchConnectionChangedEvent(chatClient));
550
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
402
551
  await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
403
552
 
404
553
  const updatedMessage = { ...channels[0].messages[0] };
@@ -420,6 +569,7 @@ export const Generic = () => {
420
569
 
421
570
  renderComponent();
422
571
  act(() => dispatchConnectionChangedEvent(chatClient));
572
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
423
573
  await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
424
574
  const removedChannel = channels[getRandomInt(0, channels.length - 1)].channel;
425
575
  act(() => dispatchNotificationRemovedFromChannel(chatClient, removedChannel));
@@ -441,11 +591,122 @@ export const Generic = () => {
441
591
  });
442
592
  });
443
593
 
594
+ it('should remove the channel from DB if the channel is deleted', async () => {
595
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
596
+
597
+ renderComponent();
598
+ act(() => dispatchConnectionChangedEvent(chatClient));
599
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
600
+ await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
601
+ const removedChannel = channels[getRandomInt(0, channels.length - 1)].channel;
602
+ act(() => dispatchChannelDeletedEvent(chatClient, removedChannel));
603
+ await waitFor(async () => {
604
+ const channelIdsOnUI = screen
605
+ .queryAllByLabelText('list-item')
606
+ .map((node) => node._fiber.pendingProps.testID);
607
+ expect(channelIdsOnUI.includes(removedChannel.cid)).toBeFalsy();
608
+ await expectCIDsOnUIToBeInDB(screen.queryAllByLabelText);
609
+
610
+ const channelsRows = await BetterSqlite.selectFromTable('channels');
611
+ const matchingRows = channelsRows.filter((c) => c.id === removedChannel.id);
612
+
613
+ const messagesRows = await BetterSqlite.selectFromTable('messages');
614
+ const matchingMessagesRows = messagesRows.filter((m) => m.cid === removedChannel.cid);
615
+
616
+ expect(matchingRows.length).toBe(0);
617
+ expect(matchingMessagesRows.length).toBe(0);
618
+ });
619
+ });
620
+
621
+ it('should correctly mark the channel as hidden in the db', async () => {
622
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
623
+
624
+ renderComponent();
625
+ act(() => dispatchConnectionChangedEvent(chatClient));
626
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
627
+ await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
628
+ const hiddenChannel = channels[getRandomInt(0, channels.length - 1)].channel;
629
+ act(() => dispatchChannelHiddenEvent(chatClient, hiddenChannel));
630
+ await waitFor(async () => {
631
+ const channelIdsOnUI = screen
632
+ .queryAllByLabelText('list-item')
633
+ .map((node) => node._fiber.pendingProps.testID);
634
+ expect(channelIdsOnUI.includes(hiddenChannel.cid)).toBeFalsy();
635
+ await expectCIDsOnUIToBeInDB(screen.queryAllByLabelText);
636
+
637
+ const channelsRows = await BetterSqlite.selectFromTable('channels');
638
+ const matchingRows = channelsRows.filter((c) => c.id === hiddenChannel.id);
639
+
640
+ const messagesRows = await BetterSqlite.selectFromTable('messages');
641
+ const matchingMessagesRows = messagesRows.filter((m) => m.cid === hiddenChannel.cid);
642
+
643
+ expect(matchingRows.length).toBe(1);
644
+ expect(matchingRows[0].hidden).toBeTruthy();
645
+ expect(matchingMessagesRows.length).toBe(
646
+ chatClient.activeChannels[hiddenChannel.cid].state.messages.length,
647
+ );
648
+ });
649
+ });
650
+
651
+ it('should correctly mark the channel as visible if it was hidden before in the db', async () => {
652
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
653
+
654
+ renderComponent();
655
+ act(() => dispatchConnectionChangedEvent(chatClient));
656
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
657
+ await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
658
+ const hiddenChannel = channels[getRandomInt(0, channels.length - 1)].channel;
659
+ // first, we mark it as hidden
660
+ act(() => dispatchChannelHiddenEvent(chatClient, hiddenChannel));
661
+ await waitFor(async () => {
662
+ const channelIdsOnUI = screen
663
+ .queryAllByLabelText('list-item')
664
+ .map((node) => node._fiber.pendingProps.testID);
665
+ expect(channelIdsOnUI.includes(hiddenChannel.cid)).toBeFalsy();
666
+ await expectCIDsOnUIToBeInDB(screen.queryAllByLabelText);
667
+
668
+ const channelsRows = await BetterSqlite.selectFromTable('channels');
669
+ const matchingRows = channelsRows.filter((c) => c.id === hiddenChannel.id);
670
+
671
+ const messagesRows = await BetterSqlite.selectFromTable('messages');
672
+ const matchingMessagesRows = messagesRows.filter((m) => m.cid === hiddenChannel.cid);
673
+
674
+ expect(matchingRows.length).toBe(1);
675
+ expect(matchingRows[0].hidden).toBeTruthy();
676
+ expect(matchingMessagesRows.length).toBe(
677
+ chatClient.activeChannels[hiddenChannel.cid].state.messages.length,
678
+ );
679
+ });
680
+
681
+ // then, we make it visible after waiting for everything to finish
682
+ act(() => dispatchChannelVisibleEvent(chatClient, hiddenChannel));
683
+ await waitFor(async () => {
684
+ const channelIdsOnUI = screen
685
+ .queryAllByLabelText('list-item')
686
+ .map((node) => node._fiber.pendingProps.testID);
687
+ expect(channelIdsOnUI.includes(hiddenChannel.cid)).toBeFalsy();
688
+ await expectCIDsOnUIToBeInDB(screen.queryAllByLabelText);
689
+
690
+ const channelsRows = await BetterSqlite.selectFromTable('channels');
691
+ const matchingRows = channelsRows.filter((c) => c.id === hiddenChannel.id);
692
+
693
+ const messagesRows = await BetterSqlite.selectFromTable('messages');
694
+ const matchingMessagesRows = messagesRows.filter((m) => m.cid === hiddenChannel.cid);
695
+
696
+ expect(matchingRows.length).toBe(1);
697
+ expect(matchingRows[0].hidden).toBeFalsy();
698
+ expect(matchingMessagesRows.length).toBe(
699
+ chatClient.activeChannels[hiddenChannel.cid].state.messages.length,
700
+ );
701
+ });
702
+ });
703
+
444
704
  it('should add the channel to DB when user is added as member', async () => {
445
705
  useMockedApis(chatClient, [queryChannelsApi(channels)]);
446
706
 
447
707
  renderComponent();
448
708
  act(() => dispatchConnectionChangedEvent(chatClient));
709
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
449
710
  await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
450
711
 
451
712
  const newChannel = createChannel();
@@ -476,11 +737,130 @@ export const Generic = () => {
476
737
 
477
738
  renderComponent();
478
739
  act(() => dispatchConnectionChangedEvent(chatClient));
740
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
479
741
  await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
480
742
 
481
743
  const channelToTruncate = channels[getRandomInt(0, channels.length - 1)].channel;
482
744
  act(() => dispatchChannelTruncatedEvent(chatClient, channelToTruncate));
483
745
 
746
+ await waitFor(async () => {
747
+ const channelIdsOnUI = screen
748
+ .queryAllByLabelText('list-item')
749
+ .map((node) => node._fiber.pendingProps.testID);
750
+ expect(channelIdsOnUI.includes(channelToTruncate.cid)).toBeTruthy();
751
+ expectCIDsOnUIToBeInDB(screen.queryAllByLabelText);
752
+
753
+ const messagesRows = await BetterSqlite.selectFromTable('messages');
754
+ const matchingMessagesRows = messagesRows.filter((m) => m.cid === channelToTruncate.cid);
755
+
756
+ const readsRows = await BetterSqlite.selectFromTable('reads');
757
+ const matchingReadRows = readsRows.filter(
758
+ (r) => r.userId === chatClient.userID && r.cid === channelToTruncate.cid,
759
+ );
760
+
761
+ expect(matchingMessagesRows.length).toBe(0);
762
+ expect(matchingReadRows.length).toBe(1);
763
+ expect(matchingReadRows[0].unreadMessages).toBe(0);
764
+ });
765
+ });
766
+
767
+ it('should truncate the correct messages if channel.truncated arrives with truncated_at', async () => {
768
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
769
+
770
+ renderComponent();
771
+ act(() => dispatchConnectionChangedEvent(chatClient));
772
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
773
+ await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
774
+
775
+ const channelResponse = channels[getRandomInt(0, channels.length - 1)];
776
+ const channelToTruncate = channelResponse.channel;
777
+ const messages = channelResponse.messages;
778
+ messages.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
779
+ // truncate at the middle
780
+ const truncatedAt = messages[Number(messages.length / 2)].created_at;
781
+ act(() =>
782
+ dispatchChannelTruncatedEvent(chatClient, {
783
+ ...channelToTruncate,
784
+ truncated_at: truncatedAt,
785
+ }),
786
+ );
787
+
788
+ await waitFor(async () => {
789
+ const channelIdsOnUI = screen
790
+ .queryAllByLabelText('list-item')
791
+ .map((node) => node._fiber.pendingProps.testID);
792
+ expect(channelIdsOnUI.includes(channelToTruncate.cid)).toBeTruthy();
793
+ expectCIDsOnUIToBeInDB(screen.queryAllByLabelText);
794
+
795
+ const messagesRows = await BetterSqlite.selectFromTable('messages');
796
+ const matchingMessagesRows = messagesRows.filter((m) => m.cid === channelToTruncate.cid);
797
+
798
+ const readsRows = await BetterSqlite.selectFromTable('reads');
799
+ const matchingReadRows = readsRows.filter(
800
+ (r) => r.userId === chatClient.userID && r.cid === channelToTruncate.cid,
801
+ );
802
+
803
+ const messagesLeft = messages.length / 2 - 1;
804
+
805
+ expect(matchingMessagesRows.length).toBe(messagesLeft);
806
+ expect(matchingReadRows.length).toBe(1);
807
+ expect(matchingReadRows[0].unreadMessages).toBe(messagesLeft);
808
+ });
809
+ });
810
+
811
+ it('should gracefully handle a truncated_at date before each message', async () => {
812
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
813
+
814
+ renderComponent();
815
+ act(() => dispatchConnectionChangedEvent(chatClient));
816
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
817
+ await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
818
+
819
+ const channelResponse = channels[getRandomInt(0, channels.length - 1)];
820
+ const channelToTruncate = channelResponse.channel;
821
+ const truncatedAt = new Date(0).toISOString();
822
+ act(() =>
823
+ dispatchChannelTruncatedEvent(chatClient, {
824
+ ...channelToTruncate,
825
+ truncated_at: truncatedAt,
826
+ }),
827
+ );
828
+
829
+ await waitFor(async () => {
830
+ const channelIdsOnUI = screen
831
+ .queryAllByLabelText('list-item')
832
+ .map((node) => node._fiber.pendingProps.testID);
833
+ expect(channelIdsOnUI.includes(channelToTruncate.cid)).toBeTruthy();
834
+ expectCIDsOnUIToBeInDB(screen.queryAllByLabelText);
835
+
836
+ const messagesRows = await BetterSqlite.selectFromTable('messages');
837
+ const matchingMessagesRows = messagesRows.filter((m) => m.cid === channelToTruncate.cid);
838
+
839
+ expect(matchingMessagesRows.length).toBe(channelResponse.messages.length);
840
+ });
841
+ });
842
+
843
+ it('should gracefully handle a truncated_at date after each message', async () => {
844
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
845
+
846
+ renderComponent();
847
+ act(() => dispatchConnectionChangedEvent(chatClient));
848
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
849
+ await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
850
+
851
+ const channelResponse = channels[getRandomInt(0, channels.length - 1)];
852
+ const channelToTruncate = channelResponse.channel;
853
+ const messages = channelResponse.messages;
854
+ const latestTimestamp = Math.max(...messages.map((m) => new Date(m.created_at).getTime()));
855
+ // truncate at the middle
856
+ const truncatedAt = new Date(latestTimestamp + 1).toISOString();
857
+ act(() =>
858
+ dispatchChannelTruncatedEvent(chatClient, {
859
+ ...channelToTruncate,
860
+ truncated_at: truncatedAt,
861
+ }),
862
+ );
863
+
484
864
  await waitFor(async () => {
485
865
  const channelIdsOnUI = screen
486
866
  .queryAllByLabelText('list-item')
@@ -499,6 +879,7 @@ export const Generic = () => {
499
879
  useMockedApis(chatClient, [queryChannelsApi(channels)]);
500
880
  renderComponent();
501
881
  act(() => dispatchConnectionChangedEvent(chatClient));
882
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
502
883
  await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
503
884
 
504
885
  const targetChannel = channels[getRandomInt(0, channels.length - 1)];
@@ -539,11 +920,161 @@ export const Generic = () => {
539
920
  });
540
921
  });
541
922
 
923
+ it('should correctly add multiple reactions to the DB', async () => {
924
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
925
+ renderComponent();
926
+ act(() => dispatchConnectionChangedEvent(chatClient));
927
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
928
+ await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
929
+
930
+ const targetChannel = channels[getRandomInt(0, channels.length - 1)];
931
+ const targetMessage =
932
+ targetChannel.messages[getRandomInt(0, targetChannel.messages.length - 1)];
933
+ const reactionMember =
934
+ targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)];
935
+ const someOtherMember = targetChannel.members.filter(
936
+ (member) => reactionMember.user.id !== member.user.id,
937
+ )[getRandomInt(0, targetChannel.members.length - 2)];
938
+
939
+ const newReactions = [
940
+ generateReaction({
941
+ message_id: targetMessage.id,
942
+ type: 'wow',
943
+ user: reactionMember.user,
944
+ }),
945
+ generateReaction({
946
+ message_id: targetMessage.id,
947
+ type: 'wow',
948
+ user: someOtherMember.user,
949
+ }),
950
+ generateReaction({
951
+ message_id: targetMessage.id,
952
+ type: 'love',
953
+ user: reactionMember.user,
954
+ }),
955
+ ];
956
+ const messageWithNewReactionBase = {
957
+ ...targetMessage,
958
+ latest_reactions: [...targetMessage.latest_reactions],
959
+ };
960
+ const newLatestReactions = [];
961
+
962
+ newReactions.forEach((newReaction) => {
963
+ newLatestReactions.push(newReaction);
964
+ const messageWithNewReaction = {
965
+ ...messageWithNewReactionBase,
966
+ latest_reactions: [...messageWithNewReactionBase.latest_reactions, ...newLatestReactions],
967
+ };
968
+ act(() =>
969
+ dispatchReactionNewEvent(
970
+ chatClient,
971
+ newReaction,
972
+ messageWithNewReaction,
973
+ targetChannel.channel,
974
+ ),
975
+ );
976
+ });
977
+
978
+ const finalReactionCount =
979
+ messageWithNewReactionBase.latest_reactions.length +
980
+ newReactions.filter(
981
+ (newReaction) =>
982
+ !messageWithNewReactionBase.latest_reactions.some(
983
+ (initialReaction) =>
984
+ initialReaction.type === newReaction.type &&
985
+ initialReaction.user.id === newReaction.user.id,
986
+ ),
987
+ ).length;
988
+
989
+ await waitFor(async () => {
990
+ const reactionsRows = await BetterSqlite.selectFromTable('reactions');
991
+ const matchingReactionsRows = reactionsRows.filter(
992
+ (r) => r.messageId === messageWithNewReactionBase.id,
993
+ );
994
+
995
+ expect(matchingReactionsRows.length).toBe(finalReactionCount);
996
+ newReactions.forEach((newReaction) => {
997
+ expect(
998
+ matchingReactionsRows.filter(
999
+ (reaction) =>
1000
+ reaction.type === newReaction.type && reaction.userId === newReaction.user.id,
1001
+ ).length,
1002
+ ).toBe(1);
1003
+ });
1004
+ });
1005
+ });
1006
+
1007
+ it('should gracefully handle multiple reaction.new events of the same type for the same user', async () => {
1008
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
1009
+ renderComponent();
1010
+ act(() => dispatchConnectionChangedEvent(chatClient));
1011
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
1012
+ await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
1013
+
1014
+ const targetChannel = channels[getRandomInt(0, channels.length - 1)];
1015
+ const targetMessage =
1016
+ targetChannel.messages[getRandomInt(0, targetChannel.messages.length - 1)];
1017
+ const reactionMember =
1018
+ targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)];
1019
+
1020
+ const newReactions = [
1021
+ generateReaction({
1022
+ message_id: targetMessage.id,
1023
+ type: 'wow',
1024
+ user: reactionMember.user,
1025
+ }),
1026
+ generateReaction({
1027
+ message_id: targetMessage.id,
1028
+ type: 'wow',
1029
+ user: reactionMember.user,
1030
+ }),
1031
+ generateReaction({
1032
+ message_id: targetMessage.id,
1033
+ type: 'wow',
1034
+ user: reactionMember.user,
1035
+ }),
1036
+ ];
1037
+ const messageWithNewReactionBase = {
1038
+ ...targetMessage,
1039
+ latest_reactions: [...targetMessage.latest_reactions],
1040
+ };
1041
+ const newLatestReactions = [];
1042
+
1043
+ newReactions.forEach((newReaction) => {
1044
+ newLatestReactions.push(newReaction);
1045
+ const messageWithNewReaction = {
1046
+ ...messageWithNewReactionBase,
1047
+ latest_reactions: [...messageWithNewReactionBase.latest_reactions, ...newLatestReactions],
1048
+ };
1049
+ act(() =>
1050
+ dispatchReactionNewEvent(
1051
+ chatClient,
1052
+ newReaction,
1053
+ messageWithNewReaction,
1054
+ targetChannel.channel,
1055
+ ),
1056
+ );
1057
+ });
1058
+
1059
+ await waitFor(async () => {
1060
+ const reactionsRows = await BetterSqlite.selectFromTable('reactions');
1061
+ const matchingReactionsRows = reactionsRows.filter(
1062
+ (r) =>
1063
+ r.type === 'wow' &&
1064
+ r.userId === reactionMember.user.id &&
1065
+ r.messageId === messageWithNewReactionBase.id,
1066
+ );
1067
+
1068
+ expect(matchingReactionsRows.length).toBe(1);
1069
+ });
1070
+ });
1071
+
542
1072
  it('should remove a reaction from DB when reaction is deleted', async () => {
543
1073
  useMockedApis(chatClient, [queryChannelsApi(channels)]);
544
1074
 
545
1075
  renderComponent();
546
1076
  act(() => dispatchConnectionChangedEvent(chatClient));
1077
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
547
1078
  await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
548
1079
 
549
1080
  const targetChannel = channels[getRandomInt(0, channels.length - 1)];
@@ -597,6 +1128,7 @@ export const Generic = () => {
597
1128
 
598
1129
  renderComponent();
599
1130
  act(() => dispatchConnectionChangedEvent(chatClient));
1131
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
600
1132
  await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
601
1133
 
602
1134
  const targetChannel = channels[getRandomInt(0, channels.length - 1)];
@@ -629,24 +1161,301 @@ export const Generic = () => {
629
1161
  });
630
1162
  });
631
1163
 
632
- it('should add a member to DB when a new member is added to channel', async () => {
1164
+ it('should correctly upsert reactions when enforce_unique is true', async () => {
633
1165
  useMockedApis(chatClient, [queryChannelsApi(channels)]);
1166
+ renderComponent();
1167
+ act(() => dispatchConnectionChangedEvent(chatClient));
1168
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
1169
+ await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
634
1170
 
1171
+ const targetChannel = channels[getRandomInt(0, channels.length - 1)];
1172
+ const targetMessage =
1173
+ targetChannel.messages[getRandomInt(0, targetChannel.messages.length - 1)];
1174
+ const reactionMember =
1175
+ targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)];
1176
+
1177
+ const newReactions = [
1178
+ generateReaction({
1179
+ message_id: targetMessage.id,
1180
+ type: 'wow',
1181
+ user: reactionMember.user,
1182
+ }),
1183
+ generateReaction({
1184
+ message_id: targetMessage.id,
1185
+ type: 'love',
1186
+ user: reactionMember.user,
1187
+ }),
1188
+ ];
1189
+ const messageWithNewReactionBase = {
1190
+ ...targetMessage,
1191
+ latest_reactions: [...targetMessage.latest_reactions],
1192
+ };
1193
+ const newLatestReactions = [];
1194
+
1195
+ newReactions.forEach((newReaction) => {
1196
+ newLatestReactions.push(newReaction);
1197
+ const messageWithNewReaction = {
1198
+ ...messageWithNewReactionBase,
1199
+ latest_reactions: [...messageWithNewReactionBase.latest_reactions, ...newLatestReactions],
1200
+ };
1201
+ act(() =>
1202
+ dispatchReactionNewEvent(
1203
+ chatClient,
1204
+ newReaction,
1205
+ messageWithNewReaction,
1206
+ targetChannel.channel,
1207
+ ),
1208
+ );
1209
+ });
1210
+
1211
+ await waitFor(async () => {
1212
+ const reactionsRows = await BetterSqlite.selectFromTable('reactions');
1213
+ const matchingReactionsRows = reactionsRows.filter(
1214
+ (r) =>
1215
+ r.messageId === messageWithNewReactionBase.id && r.userId === reactionMember.user.id,
1216
+ );
1217
+
1218
+ expect(matchingReactionsRows.length).toBe(2);
1219
+ newReactions.forEach((newReaction) => {
1220
+ expect(
1221
+ matchingReactionsRows.filter(
1222
+ (reaction) =>
1223
+ reaction.type === newReaction.type && reaction.userId === newReaction.user.id,
1224
+ ).length,
1225
+ ).toBe(1);
1226
+ });
1227
+ });
1228
+
1229
+ const uniqueReaction = generateReaction({
1230
+ message_id: targetMessage.id,
1231
+ type: 'like',
1232
+ user: reactionMember.user,
1233
+ });
1234
+ const messageWithNewReaction = {
1235
+ ...targetMessage,
1236
+ latest_reactions: [...targetMessage.latest_reactions, uniqueReaction],
1237
+ };
1238
+
1239
+ act(() =>
1240
+ dispatchReactionUpdatedEvent(
1241
+ chatClient,
1242
+ uniqueReaction,
1243
+ messageWithNewReaction,
1244
+ targetChannel.channel,
1245
+ ),
1246
+ );
1247
+
1248
+ await waitFor(async () => {
1249
+ const reactionsRows = await BetterSqlite.selectFromTable('reactions');
1250
+ const matchingReactionsRows = reactionsRows.filter(
1251
+ (r) =>
1252
+ r.type === uniqueReaction.type &&
1253
+ r.userId === reactionMember.user.id &&
1254
+ r.messageId === messageWithNewReaction.id,
1255
+ );
1256
+
1257
+ expect(matchingReactionsRows.length).toBe(1);
1258
+ });
1259
+ });
1260
+
1261
+ it('should also update the corresponding message.reaction_groups with reaction.new', async () => {
1262
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
635
1263
  renderComponent();
636
1264
  act(() => dispatchConnectionChangedEvent(chatClient));
1265
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
637
1266
  await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
638
1267
 
639
1268
  const targetChannel = channels[getRandomInt(0, channels.length - 1)];
1269
+ const targetMessage =
1270
+ targetChannel.messages[getRandomInt(0, targetChannel.messages.length - 1)];
1271
+ const reactionMember =
1272
+ targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)];
1273
+
1274
+ const newReaction = generateReaction({
1275
+ message_id: targetMessage.id,
1276
+ type: 'wow',
1277
+ user: reactionMember.user,
1278
+ });
1279
+ const newDate = new Date().toISOString();
1280
+ // the actual content of the reaction_groups does not matter, as we just want to know if it updates to it
1281
+ // anything impossible given the scenarios is fine
1282
+ const messageWithNewReaction = {
1283
+ ...targetMessage,
1284
+ latest_reactions: [...targetMessage.latest_reactions, newReaction],
1285
+ reaction_groups: {
1286
+ ...targetMessage.reaction_groups,
1287
+ [newReaction.type]: {
1288
+ count: 999,
1289
+ first_reaction_at: newDate,
1290
+ last_reaction_at: newDate,
1291
+ sum_scores: 999,
1292
+ },
1293
+ },
1294
+ };
1295
+
1296
+ act(() =>
1297
+ dispatchReactionNewEvent(
1298
+ chatClient,
1299
+ newReaction,
1300
+ messageWithNewReaction,
1301
+ targetChannel.channel,
1302
+ ),
1303
+ );
1304
+
1305
+ await waitFor(async () => {
1306
+ const messageRows = await BetterSqlite.selectFromTable('messages');
1307
+ const messageWithReactionRow = messageRows.filter(
1308
+ (m) => m.id === messageWithNewReaction.id,
1309
+ )[0];
1310
+
1311
+ const reactionGroups = JSON.parse(messageWithReactionRow.reactionGroups);
1312
+
1313
+ expect(reactionGroups[newReaction.type]?.count).toBe(999);
1314
+ expect(reactionGroups[newReaction.type]?.sum_scores).toBe(999);
1315
+ expect(reactionGroups[newReaction.type]?.first_reaction_at).toBe(newDate);
1316
+ expect(reactionGroups[newReaction.type]?.last_reaction_at).toBe(newDate);
1317
+ });
1318
+ });
1319
+
1320
+ it('should also update the corresponding message.reaction_groups with reaction.updated', async () => {
1321
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
1322
+ renderComponent();
1323
+ act(() => dispatchConnectionChangedEvent(chatClient));
1324
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
1325
+ await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
1326
+
1327
+ const targetChannel = channels[getRandomInt(0, channels.length - 1)];
1328
+ const targetMessage =
1329
+ targetChannel.messages[getRandomInt(0, targetChannel.messages.length - 1)];
1330
+ const reactionMember =
1331
+ targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)];
1332
+
1333
+ const newReaction = generateReaction({
1334
+ message_id: targetMessage.id,
1335
+ type: 'wow',
1336
+ user: reactionMember.user,
1337
+ });
1338
+ const newDate = new Date().toISOString();
1339
+ const messageWithNewReaction = {
1340
+ ...targetMessage,
1341
+ latest_reactions: [...targetMessage.latest_reactions, newReaction],
1342
+ reaction_groups: {
1343
+ ...targetMessage.reaction_groups,
1344
+ [newReaction.type]: {
1345
+ count: 999,
1346
+ first_reaction_at: newDate,
1347
+ last_reaction_at: newDate,
1348
+ sum_scores: 999,
1349
+ },
1350
+ },
1351
+ };
1352
+
1353
+ act(() =>
1354
+ dispatchReactionUpdatedEvent(
1355
+ chatClient,
1356
+ newReaction,
1357
+ messageWithNewReaction,
1358
+ targetChannel.channel,
1359
+ ),
1360
+ );
1361
+
1362
+ await waitFor(async () => {
1363
+ const messageRows = await BetterSqlite.selectFromTable('messages');
1364
+ const messageWithReactionRow = messageRows.filter(
1365
+ (m) => m.id === messageWithNewReaction.id,
1366
+ )[0];
1367
+
1368
+ const reactionGroups = JSON.parse(messageWithReactionRow.reactionGroups);
1369
+
1370
+ expect(reactionGroups[newReaction.type]?.count).toBe(999);
1371
+ expect(reactionGroups[newReaction.type]?.sum_scores).toBe(999);
1372
+ expect(reactionGroups[newReaction.type]?.first_reaction_at).toBe(newDate);
1373
+ expect(reactionGroups[newReaction.type]?.last_reaction_at).toBe(newDate);
1374
+ });
1375
+ });
1376
+
1377
+ it('should also update the corresponding message.reaction_groups with reaction.deleted', async () => {
1378
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
1379
+ renderComponent();
1380
+ act(() => dispatchConnectionChangedEvent(chatClient));
1381
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
1382
+ await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
1383
+
1384
+ const targetChannel = channels[getRandomInt(0, channels.length - 1)];
1385
+ const targetMessage =
1386
+ targetChannel.messages[getRandomInt(0, targetChannel.messages.length - 1)];
1387
+ const reactionMember =
1388
+ targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)];
1389
+
1390
+ const newReaction = generateReaction({
1391
+ message_id: targetMessage.id,
1392
+ type: 'wow',
1393
+ user: reactionMember.user,
1394
+ });
1395
+ const newDate = new Date().toISOString();
1396
+ const messageWithNewReaction = {
1397
+ ...targetMessage,
1398
+ latest_reactions: [...targetMessage.latest_reactions, newReaction],
1399
+ reaction_groups: {
1400
+ ...targetMessage.reaction_groups,
1401
+ [newReaction.type]: {
1402
+ count: 999,
1403
+ first_reaction_at: newDate,
1404
+ last_reaction_at: newDate,
1405
+ sum_scores: 999,
1406
+ },
1407
+ },
1408
+ };
1409
+
1410
+ act(() =>
1411
+ dispatchReactionDeletedEvent(
1412
+ chatClient,
1413
+ newReaction,
1414
+ messageWithNewReaction,
1415
+ targetChannel.channel,
1416
+ ),
1417
+ );
1418
+
1419
+ await waitFor(async () => {
1420
+ const messageRows = await BetterSqlite.selectFromTable('messages');
1421
+ const messageWithReactionRow = messageRows.filter(
1422
+ (m) => m.id === messageWithNewReaction.id,
1423
+ )[0];
1424
+
1425
+ const reactionGroups = JSON.parse(messageWithReactionRow.reactionGroups);
1426
+
1427
+ expect(reactionGroups[newReaction.type]?.count).toBe(999);
1428
+ expect(reactionGroups[newReaction.type]?.sum_scores).toBe(999);
1429
+ expect(reactionGroups[newReaction.type]?.first_reaction_at).toBe(newDate);
1430
+ expect(reactionGroups[newReaction.type]?.last_reaction_at).toBe(newDate);
1431
+ });
1432
+ });
1433
+
1434
+ it('should add a member to DB when a new member is added to channel', async () => {
1435
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
1436
+ renderComponent();
1437
+
1438
+ act(() => dispatchConnectionChangedEvent(chatClient));
1439
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
1440
+ await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
1441
+ const targetChannel = channels[getRandomInt(0, channels.length - 1)];
1442
+
1443
+ const oldMemberCount = targetChannel.channel.member_count;
640
1444
  const newMember = generateMember();
641
1445
  act(() => dispatchMemberAddedEvent(chatClient, newMember, targetChannel.channel));
642
1446
 
643
1447
  await waitFor(async () => {
644
1448
  const membersRows = await BetterSqlite.selectFromTable('members');
1449
+ const channelRows = await BetterSqlite.selectFromTable('channels');
645
1450
  const matchingMembersRows = membersRows.filter(
646
1451
  (m) => m.cid === targetChannel.channel.cid && m.userId === newMember.user_id,
647
1452
  );
1453
+ const targetChannelFromDb = channelRows.filter(
1454
+ (c) => c.cid === targetChannel.channel.cid,
1455
+ )[0];
648
1456
 
649
1457
  expect(matchingMembersRows.length).toBe(1);
1458
+ expect(targetChannelFromDb.memberCount).toBe(oldMemberCount + 1);
650
1459
  });
651
1460
  });
652
1461
 
@@ -655,19 +1464,26 @@ export const Generic = () => {
655
1464
 
656
1465
  renderComponent();
657
1466
  act(() => dispatchConnectionChangedEvent(chatClient));
1467
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
658
1468
  await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
659
1469
 
660
1470
  const targetChannel = channels[getRandomInt(0, channels.length - 1)];
661
1471
  const targetMember = targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)];
1472
+ const oldMemberCount = targetChannel.channel.member_count;
662
1473
  act(() => dispatchMemberRemovedEvent(chatClient, targetMember, targetChannel.channel));
663
1474
 
664
1475
  await waitFor(async () => {
665
1476
  const membersRows = await BetterSqlite.selectFromTable('members');
1477
+ const channelRows = await BetterSqlite.selectFromTable('channels');
666
1478
  const matchingMembersRows = membersRows.filter(
667
1479
  (m) => m.cid === targetChannel.channel.cid && m.userId === targetMember.user_id,
668
1480
  );
1481
+ const targetChannelFromDb = channelRows.filter(
1482
+ (c) => c.cid === targetChannel.channel.cid,
1483
+ )[0];
669
1484
 
670
1485
  expect(matchingMembersRows.length).toBe(0);
1486
+ expect(targetChannelFromDb.memberCount).toBe(oldMemberCount - 1);
671
1487
  });
672
1488
  });
673
1489
 
@@ -676,6 +1492,7 @@ export const Generic = () => {
676
1492
 
677
1493
  renderComponent();
678
1494
  act(() => dispatchConnectionChangedEvent(chatClient));
1495
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
679
1496
  await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
680
1497
 
681
1498
  const targetChannel = channels[getRandomInt(0, channels.length - 1)];
@@ -702,6 +1519,7 @@ export const Generic = () => {
702
1519
 
703
1520
  renderComponent();
704
1521
  act(() => dispatchConnectionChangedEvent(chatClient));
1522
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
705
1523
  await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
706
1524
 
707
1525
  const targetChannel = channels[getRandomInt(0, channels.length - 1)];
@@ -727,12 +1545,19 @@ export const Generic = () => {
727
1545
 
728
1546
  renderComponent();
729
1547
  act(() => dispatchConnectionChangedEvent(chatClient));
1548
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
730
1549
  await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
731
1550
  const targetChannel = channels[getRandomInt(0, channels.length - 1)];
732
1551
  const targetMember = targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)];
733
1552
 
1553
+ const readTimestamp = new Date().toISOString();
1554
+
734
1555
  act(() => {
735
- dispatchMessageReadEvent(chatClient, targetMember.user, targetChannel.channel);
1556
+ dispatchMessageReadEvent(chatClient, targetMember.user, targetChannel.channel, {
1557
+ first_unread_message_id: '123',
1558
+ last_read: readTimestamp,
1559
+ last_read_message_id: '321',
1560
+ });
736
1561
  });
737
1562
 
738
1563
  await waitFor(async () => {
@@ -743,6 +1568,54 @@ export const Generic = () => {
743
1568
 
744
1569
  expect(matchingReadRows.length).toBe(1);
745
1570
  expect(matchingReadRows[0].unreadMessages).toBe(0);
1571
+ expect(matchingReadRows[0].lastReadMessageId).toBe('321');
1572
+ // FIXME: Currently missing from the DB, uncomment when added.
1573
+ // expect(matchingReadRows[0].firstUnreadMessageId).toBe('123');
1574
+ expect(matchingReadRows[0].lastRead).toBe(readTimestamp);
1575
+ });
1576
+ });
1577
+
1578
+ it('should update reads in DB when a channel is marked as unread', async () => {
1579
+ useMockedApis(chatClient, [queryChannelsApi(channels)]);
1580
+
1581
+ renderComponent();
1582
+ act(() => dispatchConnectionChangedEvent(chatClient));
1583
+ await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
1584
+ await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy());
1585
+ const targetChannel = channels[getRandomInt(0, channels.length - 1)];
1586
+ const targetMember = targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)];
1587
+
1588
+ chatClient.userID = targetMember.user.id;
1589
+ chatClient.user = targetMember.user;
1590
+
1591
+ const readTimestamp = new Date().toISOString();
1592
+
1593
+ act(() => {
1594
+ dispatchNotificationMarkUnread(
1595
+ chatClient,
1596
+ targetChannel.channel,
1597
+ {
1598
+ first_unread_message_id: '123',
1599
+ last_read: readTimestamp,
1600
+ last_read_message_id: '321',
1601
+ unread_messages: 5,
1602
+ },
1603
+ targetMember.user,
1604
+ );
1605
+ });
1606
+
1607
+ await waitFor(async () => {
1608
+ const readsRows = await BetterSqlite.selectFromTable('reads');
1609
+ const matchingReadRows = readsRows.filter(
1610
+ (r) => r.userId === targetMember.user_id && r.cid === targetChannel.cid,
1611
+ );
1612
+
1613
+ expect(matchingReadRows.length).toBe(1);
1614
+ expect(matchingReadRows[0].unreadMessages).toBe(5);
1615
+ expect(matchingReadRows[0].lastReadMessageId).toBe('321');
1616
+ // FIXME: Currently missing from the DB, uncomment when added.
1617
+ // expect(matchingReadRows[0].firstUnreadMessageId).toBe('123');
1618
+ expect(matchingReadRows[0].lastRead).toBe(readTimestamp);
746
1619
  });
747
1620
  });
748
1621
  });