stream-chat-react-native-core 10.0.0-rc.4 → 10.0.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 (276) hide show
  1. package/lib/commonjs/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.js +4 -1
  2. package/lib/commonjs/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.js.map +1 -1
  3. package/lib/commonjs/components/Channel/hooks/useChannelLifecycle.js +2 -2
  4. package/lib/commonjs/components/Channel/hooks/useChannelLifecycle.js.map +1 -1
  5. package/lib/commonjs/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js +7 -0
  6. package/lib/commonjs/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js.map +1 -1
  7. package/lib/commonjs/components/ChannelPreview/ChannelPreviewMessage.js +4 -5
  8. package/lib/commonjs/components/ChannelPreview/ChannelPreviewMessage.js.map +1 -1
  9. package/lib/commonjs/components/ChannelPreview/hooks/useChannelPreviewPollLabel.js +8 -5
  10. package/lib/commonjs/components/ChannelPreview/hooks/useChannelPreviewPollLabel.js.map +1 -1
  11. package/lib/commonjs/components/Message/Message.js.map +1 -1
  12. package/lib/commonjs/components/Message/MessageItemView/Headers/SentToChannelHeader.js +6 -32
  13. package/lib/commonjs/components/Message/MessageItemView/Headers/SentToChannelHeader.js.map +1 -1
  14. package/lib/commonjs/components/Message/MessageItemView/MessageContent.js +2 -2
  15. package/lib/commonjs/components/Message/MessageItemView/MessageContent.js.map +1 -1
  16. package/lib/commonjs/components/Message/MessageItemView/MessageStatus.js +29 -9
  17. package/lib/commonjs/components/Message/MessageItemView/MessageStatus.js.map +1 -1
  18. package/lib/commonjs/components/Message/hooks/useOpenThreadAtMessage.js +57 -0
  19. package/lib/commonjs/components/Message/hooks/useOpenThreadAtMessage.js.map +1 -0
  20. package/lib/commonjs/components/Message/utils/messageActions.js +1 -1
  21. package/lib/commonjs/components/Message/utils/messageActions.js.map +1 -1
  22. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js +12 -8
  23. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js.map +1 -1
  24. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js +3 -2
  25. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js.map +1 -1
  26. package/lib/commonjs/components/MessageInput/hooks/useAudioRecorder.js +2 -2
  27. package/lib/commonjs/components/MessageInput/hooks/useAudioRecorder.js.map +1 -1
  28. package/lib/commonjs/components/MessageList/InlineUnreadIndicator.js +2 -2
  29. package/lib/commonjs/components/MessageList/InlineUnreadIndicator.js.map +1 -1
  30. package/lib/commonjs/components/MessageList/MessageList.js +1 -4
  31. package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
  32. package/lib/commonjs/components/MessageList/hooks/useMessageListFocus.js +18 -2
  33. package/lib/commonjs/components/MessageList/hooks/useMessageListFocus.js.map +1 -1
  34. package/lib/commonjs/components/Poll/Poll.js +5 -2
  35. package/lib/commonjs/components/Poll/Poll.js.map +1 -1
  36. package/lib/commonjs/components/Poll/components/PollAnswersList.js +4 -2
  37. package/lib/commonjs/components/Poll/components/PollAnswersList.js.map +1 -1
  38. package/lib/commonjs/components/Poll/components/PollOption.js +8 -3
  39. package/lib/commonjs/components/Poll/components/PollOption.js.map +1 -1
  40. package/lib/commonjs/components/Poll/components/PollResults/PollOptionFullResults.js +5 -2
  41. package/lib/commonjs/components/Poll/components/PollResults/PollOptionFullResults.js.map +1 -1
  42. package/lib/commonjs/components/Poll/components/PollResults/PollResultItem.js +3 -1
  43. package/lib/commonjs/components/Poll/components/PollResults/PollResultItem.js.map +1 -1
  44. package/lib/commonjs/components/Poll/components/PollResults/PollResults.js +4 -1
  45. package/lib/commonjs/components/Poll/components/PollResults/PollResults.js.map +1 -1
  46. package/lib/commonjs/components/Poll/hooks/usePollState.js +3 -0
  47. package/lib/commonjs/components/Poll/hooks/usePollState.js.map +1 -1
  48. package/lib/commonjs/components/Poll/hooks/usePollTextTranslation.js +17 -0
  49. package/lib/commonjs/components/Poll/hooks/usePollTextTranslation.js.map +1 -0
  50. package/lib/commonjs/components/Poll/index.js +11 -0
  51. package/lib/commonjs/components/Poll/index.js.map +1 -1
  52. package/lib/commonjs/components/Thread/components/ThreadFooterComponent.js +1 -2
  53. package/lib/commonjs/components/Thread/components/ThreadFooterComponent.js.map +1 -1
  54. package/lib/commonjs/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.js +0 -15
  55. package/lib/commonjs/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.js.map +1 -1
  56. package/lib/commonjs/contexts/messageContext/MessageContext.js.map +1 -1
  57. package/lib/commonjs/contexts/messageListItemContext/MessageListItemContext.js.map +1 -1
  58. package/lib/commonjs/hooks/messagePreview/useMessagePreviewText.js +9 -4
  59. package/lib/commonjs/hooks/messagePreview/useMessagePreviewText.js.map +1 -1
  60. package/lib/commonjs/hooks/useTranslatedMessage.js +4 -3
  61. package/lib/commonjs/hooks/useTranslatedMessage.js.map +1 -1
  62. package/lib/commonjs/index.js +0 -12
  63. package/lib/commonjs/index.js.map +1 -1
  64. package/lib/commonjs/store/SqliteClient.js +1 -1
  65. package/lib/commonjs/store/mappers/mapPollToStorable.js +4 -0
  66. package/lib/commonjs/store/mappers/mapPollToStorable.js.map +1 -1
  67. package/lib/commonjs/store/mappers/mapStorableToPoll.js +5 -1
  68. package/lib/commonjs/store/mappers/mapStorableToPoll.js.map +1 -1
  69. package/lib/commonjs/store/schema.js +2 -0
  70. package/lib/commonjs/store/schema.js.map +1 -1
  71. package/lib/commonjs/types/types.js.map +1 -1
  72. package/lib/commonjs/utils/i18n/getTranslatedText.js +7 -0
  73. package/lib/commonjs/utils/i18n/getTranslatedText.js.map +1 -0
  74. package/lib/commonjs/utils/utils.js +35 -1
  75. package/lib/commonjs/utils/utils.js.map +1 -1
  76. package/lib/commonjs/version.json +1 -1
  77. package/lib/module/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.js +4 -1
  78. package/lib/module/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.js.map +1 -1
  79. package/lib/module/components/Channel/hooks/useChannelLifecycle.js +2 -2
  80. package/lib/module/components/Channel/hooks/useChannelLifecycle.js.map +1 -1
  81. package/lib/module/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js +7 -0
  82. package/lib/module/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js.map +1 -1
  83. package/lib/module/components/ChannelPreview/ChannelPreviewMessage.js +4 -5
  84. package/lib/module/components/ChannelPreview/ChannelPreviewMessage.js.map +1 -1
  85. package/lib/module/components/ChannelPreview/hooks/useChannelPreviewPollLabel.js +8 -5
  86. package/lib/module/components/ChannelPreview/hooks/useChannelPreviewPollLabel.js.map +1 -1
  87. package/lib/module/components/Message/Message.js.map +1 -1
  88. package/lib/module/components/Message/MessageItemView/Headers/SentToChannelHeader.js +6 -32
  89. package/lib/module/components/Message/MessageItemView/Headers/SentToChannelHeader.js.map +1 -1
  90. package/lib/module/components/Message/MessageItemView/MessageContent.js +2 -2
  91. package/lib/module/components/Message/MessageItemView/MessageContent.js.map +1 -1
  92. package/lib/module/components/Message/MessageItemView/MessageStatus.js +29 -9
  93. package/lib/module/components/Message/MessageItemView/MessageStatus.js.map +1 -1
  94. package/lib/module/components/Message/hooks/useOpenThreadAtMessage.js +57 -0
  95. package/lib/module/components/Message/hooks/useOpenThreadAtMessage.js.map +1 -0
  96. package/lib/module/components/Message/utils/messageActions.js +1 -1
  97. package/lib/module/components/Message/utils/messageActions.js.map +1 -1
  98. package/lib/module/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js +12 -8
  99. package/lib/module/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js.map +1 -1
  100. package/lib/module/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js +3 -2
  101. package/lib/module/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js.map +1 -1
  102. package/lib/module/components/MessageInput/hooks/useAudioRecorder.js +2 -2
  103. package/lib/module/components/MessageInput/hooks/useAudioRecorder.js.map +1 -1
  104. package/lib/module/components/MessageList/InlineUnreadIndicator.js +2 -2
  105. package/lib/module/components/MessageList/InlineUnreadIndicator.js.map +1 -1
  106. package/lib/module/components/MessageList/MessageList.js +1 -4
  107. package/lib/module/components/MessageList/MessageList.js.map +1 -1
  108. package/lib/module/components/MessageList/hooks/useMessageListFocus.js +18 -2
  109. package/lib/module/components/MessageList/hooks/useMessageListFocus.js.map +1 -1
  110. package/lib/module/components/Poll/Poll.js +5 -2
  111. package/lib/module/components/Poll/Poll.js.map +1 -1
  112. package/lib/module/components/Poll/components/PollAnswersList.js +4 -2
  113. package/lib/module/components/Poll/components/PollAnswersList.js.map +1 -1
  114. package/lib/module/components/Poll/components/PollOption.js +8 -3
  115. package/lib/module/components/Poll/components/PollOption.js.map +1 -1
  116. package/lib/module/components/Poll/components/PollResults/PollOptionFullResults.js +5 -2
  117. package/lib/module/components/Poll/components/PollResults/PollOptionFullResults.js.map +1 -1
  118. package/lib/module/components/Poll/components/PollResults/PollResultItem.js +3 -1
  119. package/lib/module/components/Poll/components/PollResults/PollResultItem.js.map +1 -1
  120. package/lib/module/components/Poll/components/PollResults/PollResults.js +4 -1
  121. package/lib/module/components/Poll/components/PollResults/PollResults.js.map +1 -1
  122. package/lib/module/components/Poll/hooks/usePollState.js +3 -0
  123. package/lib/module/components/Poll/hooks/usePollState.js.map +1 -1
  124. package/lib/module/components/Poll/hooks/usePollTextTranslation.js +17 -0
  125. package/lib/module/components/Poll/hooks/usePollTextTranslation.js.map +1 -0
  126. package/lib/module/components/Poll/index.js +11 -0
  127. package/lib/module/components/Poll/index.js.map +1 -1
  128. package/lib/module/components/Thread/components/ThreadFooterComponent.js +1 -2
  129. package/lib/module/components/Thread/components/ThreadFooterComponent.js.map +1 -1
  130. package/lib/module/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.js +0 -15
  131. package/lib/module/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.js.map +1 -1
  132. package/lib/module/contexts/messageContext/MessageContext.js.map +1 -1
  133. package/lib/module/contexts/messageListItemContext/MessageListItemContext.js.map +1 -1
  134. package/lib/module/hooks/messagePreview/useMessagePreviewText.js +9 -4
  135. package/lib/module/hooks/messagePreview/useMessagePreviewText.js.map +1 -1
  136. package/lib/module/hooks/useTranslatedMessage.js +4 -3
  137. package/lib/module/hooks/useTranslatedMessage.js.map +1 -1
  138. package/lib/module/index.js +0 -12
  139. package/lib/module/index.js.map +1 -1
  140. package/lib/module/store/SqliteClient.js +1 -1
  141. package/lib/module/store/mappers/mapPollToStorable.js +4 -0
  142. package/lib/module/store/mappers/mapPollToStorable.js.map +1 -1
  143. package/lib/module/store/mappers/mapStorableToPoll.js +5 -1
  144. package/lib/module/store/mappers/mapStorableToPoll.js.map +1 -1
  145. package/lib/module/store/schema.js +2 -0
  146. package/lib/module/store/schema.js.map +1 -1
  147. package/lib/module/types/types.js.map +1 -1
  148. package/lib/module/utils/i18n/getTranslatedText.js +7 -0
  149. package/lib/module/utils/i18n/getTranslatedText.js.map +1 -0
  150. package/lib/module/utils/utils.js +35 -1
  151. package/lib/module/utils/utils.js.map +1 -1
  152. package/lib/module/version.json +1 -1
  153. package/lib/typescript/ai-components/services/media-picker-service/index.d.ts +1 -1
  154. package/lib/typescript/components/Channel/hooks/useChannelLifecycle.d.ts.map +1 -1
  155. package/lib/typescript/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.d.ts.map +1 -1
  156. package/lib/typescript/components/ChannelPreview/ChannelPreviewMessage.d.ts.map +1 -1
  157. package/lib/typescript/components/ChannelPreview/hooks/useChannelPreviewPollLabel.d.ts.map +1 -1
  158. package/lib/typescript/components/Message/Message.d.ts +2 -1
  159. package/lib/typescript/components/Message/Message.d.ts.map +1 -1
  160. package/lib/typescript/components/Message/MessageItemView/Headers/SentToChannelHeader.d.ts.map +1 -1
  161. package/lib/typescript/components/Message/MessageItemView/MessageContent.d.ts.map +1 -1
  162. package/lib/typescript/components/Message/MessageItemView/MessageStatus.d.ts +6 -0
  163. package/lib/typescript/components/Message/MessageItemView/MessageStatus.d.ts.map +1 -1
  164. package/lib/typescript/components/Message/hooks/useOpenThreadAtMessage.d.ts +14 -0
  165. package/lib/typescript/components/Message/hooks/useOpenThreadAtMessage.d.ts.map +1 -0
  166. package/lib/typescript/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.d.ts.map +1 -1
  167. package/lib/typescript/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.d.ts.map +1 -1
  168. package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
  169. package/lib/typescript/components/MessageList/hooks/useMessageListFocus.d.ts +5 -2
  170. package/lib/typescript/components/MessageList/hooks/useMessageListFocus.d.ts.map +1 -1
  171. package/lib/typescript/components/Poll/Poll.d.ts.map +1 -1
  172. package/lib/typescript/components/Poll/components/PollAnswersList.d.ts.map +1 -1
  173. package/lib/typescript/components/Poll/components/PollOption.d.ts.map +1 -1
  174. package/lib/typescript/components/Poll/components/PollResults/PollOptionFullResults.d.ts.map +1 -1
  175. package/lib/typescript/components/Poll/components/PollResults/PollResultItem.d.ts.map +1 -1
  176. package/lib/typescript/components/Poll/components/PollResults/PollResults.d.ts.map +1 -1
  177. package/lib/typescript/components/Poll/hooks/usePollState.d.ts +1 -0
  178. package/lib/typescript/components/Poll/hooks/usePollState.d.ts.map +1 -1
  179. package/lib/typescript/components/Poll/hooks/usePollTextTranslation.d.ts +7 -0
  180. package/lib/typescript/components/Poll/hooks/usePollTextTranslation.d.ts.map +1 -0
  181. package/lib/typescript/components/Poll/index.d.ts +1 -0
  182. package/lib/typescript/components/Poll/index.d.ts.map +1 -1
  183. package/lib/typescript/components/Thread/components/ThreadFooterComponent.d.ts.map +1 -1
  184. package/lib/typescript/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.d.ts.map +1 -1
  185. package/lib/typescript/contexts/messageContext/MessageContext.d.ts +2 -1
  186. package/lib/typescript/contexts/messageContext/MessageContext.d.ts.map +1 -1
  187. package/lib/typescript/contexts/messageListItemContext/MessageListItemContext.d.ts +10 -6
  188. package/lib/typescript/contexts/messageListItemContext/MessageListItemContext.d.ts.map +1 -1
  189. package/lib/typescript/hooks/messagePreview/useMessagePreviewText.d.ts.map +1 -1
  190. package/lib/typescript/hooks/useTranslatedMessage.d.ts.map +1 -1
  191. package/lib/typescript/i18n/keys.d.ts +2 -2
  192. package/lib/typescript/i18n/keys.d.ts.map +1 -1
  193. package/lib/typescript/index.d.ts +0 -1
  194. package/lib/typescript/index.d.ts.map +1 -1
  195. package/lib/typescript/store/mappers/mapPollToStorable.d.ts.map +1 -1
  196. package/lib/typescript/store/mappers/mapStorableToPoll.d.ts.map +1 -1
  197. package/lib/typescript/store/schema.d.ts +2 -0
  198. package/lib/typescript/store/schema.d.ts.map +1 -1
  199. package/lib/typescript/types/types.d.ts +0 -4
  200. package/lib/typescript/types/types.d.ts.map +1 -1
  201. package/lib/typescript/utils/i18n/getTranslatedText.d.ts +8 -0
  202. package/lib/typescript/utils/i18n/getTranslatedText.d.ts.map +1 -0
  203. package/lib/typescript/utils/utils.d.ts.map +1 -1
  204. package/package.json +2 -2
  205. package/src/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.tsx +1 -1
  206. package/src/components/AttachmentPicker/components/AttachmentMediaPicker/__tests__/AttachmentPickerItem.test.tsx +29 -1
  207. package/src/components/Channel/__tests__/Channel.test.tsx +40 -7
  208. package/src/components/Channel/hooks/useChannelLifecycle.ts +8 -4
  209. package/src/components/Channel/hooks/useCreateOwnCapabilitiesContext.ts +1 -1
  210. package/src/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.tsx +8 -0
  211. package/src/components/ChannelPreview/ChannelPreviewMessage.tsx +2 -7
  212. package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.tsx +41 -0
  213. package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewPollLabel.test.tsx +28 -3
  214. package/src/components/ChannelPreview/hooks/useChannelPreviewPollLabel.ts +8 -4
  215. package/src/components/Message/Message.tsx +5 -2
  216. package/src/components/Message/MessageItemView/Headers/SentToChannelHeader.tsx +4 -32
  217. package/src/components/Message/MessageItemView/MessageContent.tsx +3 -2
  218. package/src/components/Message/MessageItemView/MessageStatus.tsx +43 -4
  219. package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx +68 -1
  220. package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx +57 -0
  221. package/src/components/Message/hooks/useOpenThreadAtMessage.ts +50 -0
  222. package/src/components/Message/utils/__tests__/messageActions.test.ts +57 -0
  223. package/src/components/Message/utils/messageActions.ts +1 -1
  224. package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.tsx +34 -6
  225. package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.tsx +24 -3
  226. package/src/components/MessageInput/__tests__/SendMessageDisallowedIndicator.test.tsx +3 -6
  227. package/src/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.tsx +19 -8
  228. package/src/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.tsx +2 -5
  229. package/src/components/MessageInput/hooks/useAudioRecorder.tsx +2 -2
  230. package/src/components/MessageList/InlineUnreadIndicator.tsx +2 -2
  231. package/src/components/MessageList/MessageList.tsx +1 -5
  232. package/src/components/MessageList/__tests__/InlineUnreadIndicator.test.tsx +2 -2
  233. package/src/components/MessageList/__tests__/MessageList.test.tsx +163 -0
  234. package/src/components/MessageList/hooks/useMessageListFocus.ts +22 -2
  235. package/src/components/Poll/Poll.tsx +4 -2
  236. package/src/components/Poll/__tests__/PollTranslation.test.tsx +152 -0
  237. package/src/components/Poll/components/PollAnswersList.tsx +5 -1
  238. package/src/components/Poll/components/PollOption.tsx +8 -4
  239. package/src/components/Poll/components/PollResults/PollOptionFullResults.tsx +5 -2
  240. package/src/components/Poll/components/PollResults/PollResultItem.tsx +3 -1
  241. package/src/components/Poll/components/PollResults/PollResults.tsx +4 -2
  242. package/src/components/Poll/hooks/usePollState.ts +4 -0
  243. package/src/components/Poll/hooks/usePollTextTranslation.ts +19 -0
  244. package/src/components/Poll/index.ts +1 -0
  245. package/src/components/Thread/__tests__/Thread.test.tsx +46 -1
  246. package/src/components/Thread/components/ThreadFooterComponent.tsx +0 -1
  247. package/src/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.tsx +5 -14
  248. package/src/contexts/messageContext/MessageContext.tsx +2 -1
  249. package/src/contexts/messageListItemContext/MessageListItemContext.tsx +15 -6
  250. package/src/hooks/__tests__/useTranslatedMessage.test.tsx +20 -0
  251. package/src/hooks/messagePreview/__tests__/useMessagePreviewText.test.tsx +37 -2
  252. package/src/hooks/messagePreview/useMessagePreviewText.tsx +5 -2
  253. package/src/hooks/useTranslatedMessage.ts +6 -11
  254. package/src/i18n/__tests__/catalog.fixture.json +2 -2
  255. package/src/i18n/keys.ts +2 -2
  256. package/src/index.ts +0 -1
  257. package/src/store/SqliteClient.ts +1 -1
  258. package/src/store/apis/__tests__/pollTranslations.test.ts +136 -0
  259. package/src/store/mappers/mapPollToStorable.ts +6 -0
  260. package/src/store/mappers/mapStorableToPoll.ts +4 -0
  261. package/src/store/schema.ts +4 -0
  262. package/src/types/stream-chat-common-custom-data.d.ts +0 -3
  263. package/src/types/types.ts +0 -5
  264. package/src/utils/__tests__/utils.test.ts +42 -1
  265. package/src/utils/i18n/__tests__/getTranslatedText.test.ts +17 -0
  266. package/src/utils/i18n/getTranslatedText.ts +10 -0
  267. package/src/utils/utils.ts +57 -1
  268. package/src/version.json +1 -1
  269. package/lib/commonjs/utils/setupCommandUIMiddlewares.js +0 -37
  270. package/lib/commonjs/utils/setupCommandUIMiddlewares.js.map +0 -1
  271. package/lib/module/utils/setupCommandUIMiddlewares.js +0 -37
  272. package/lib/module/utils/setupCommandUIMiddlewares.js.map +0 -1
  273. package/lib/typescript/utils/setupCommandUIMiddlewares.d.ts +0 -3
  274. package/lib/typescript/utils/setupCommandUIMiddlewares.d.ts.map +0 -1
  275. package/src/utils/__tests__/setupCommandUIMiddlewares.test.ts +0 -49
  276. package/src/utils/setupCommandUIMiddlewares.ts +0 -34
@@ -21,6 +21,7 @@ import {
21
21
  isVideoAttachment,
22
22
  LocalAttachment,
23
23
  LocalAudioAttachment,
24
+ LocalUploadAttachment,
24
25
  LocalVoiceRecordingAttachment,
25
26
  } from 'stream-chat';
26
27
 
@@ -292,6 +293,16 @@ const UnMemoizedAttachmentUploadPreviewList = () => {
292
293
  [attachmentManager, dataRef, prepareForRemoval],
293
294
  );
294
295
 
296
+ // `uploadFile` does not check whether uploads are enabled, so a retry does not start one while
297
+ // they are off.
298
+ const handleRetry = useCallback(
299
+ (attachment: LocalUploadAttachment) => {
300
+ if (!attachmentManager.isUploadEnabled) return;
301
+ return attachmentManager.uploadFile(attachment);
302
+ },
303
+ [attachmentManager],
304
+ );
305
+
295
306
  useLayoutEffect(() => {
296
307
  const previousData = previousDataRef.current;
297
308
  const nextIds = new Set(data.map((attachment) => attachment.localMetadata.id));
@@ -346,7 +357,7 @@ const UnMemoizedAttachmentUploadPreviewList = () => {
346
357
  return (
347
358
  <ImageAttachmentUploadPreview
348
359
  attachment={attachment}
349
- handleRetry={attachmentManager.uploadAttachment}
360
+ handleRetry={handleRetry}
350
361
  removeAttachments={removeAttachments}
351
362
  />
352
363
  );
@@ -354,7 +365,7 @@ const UnMemoizedAttachmentUploadPreviewList = () => {
354
365
  return (
355
366
  <AudioAttachmentUploadPreview
356
367
  attachment={attachment}
357
- handleRetry={attachmentManager.uploadAttachment}
368
+ handleRetry={handleRetry}
358
369
  removeAttachments={removeAttachments}
359
370
  />
360
371
  );
@@ -363,7 +374,7 @@ const UnMemoizedAttachmentUploadPreviewList = () => {
363
374
  return (
364
375
  <AudioAttachmentUploadPreview
365
376
  attachment={attachment}
366
- handleRetry={attachmentManager.uploadAttachment}
377
+ handleRetry={handleRetry}
367
378
  removeAttachments={removeAttachments}
368
379
  />
369
380
  );
@@ -371,7 +382,7 @@ const UnMemoizedAttachmentUploadPreviewList = () => {
371
382
  return (
372
383
  <FileAttachmentUploadPreview
373
384
  attachment={attachment}
374
- handleRetry={attachmentManager.uploadAttachment}
385
+ handleRetry={handleRetry}
375
386
  removeAttachments={removeAttachments}
376
387
  />
377
388
  );
@@ -380,7 +391,7 @@ const UnMemoizedAttachmentUploadPreviewList = () => {
380
391
  return (
381
392
  <VideoAttachmentUploadPreview
382
393
  attachment={attachment}
383
- handleRetry={attachmentManager.uploadAttachment}
394
+ handleRetry={handleRetry}
384
395
  removeAttachments={removeAttachments}
385
396
  />
386
397
  );
@@ -388,7 +399,7 @@ const UnMemoizedAttachmentUploadPreviewList = () => {
388
399
  return (
389
400
  <FileAttachmentUploadPreview
390
401
  attachment={attachment}
391
- handleRetry={attachmentManager.uploadAttachment}
402
+ handleRetry={handleRetry}
392
403
  removeAttachments={removeAttachments}
393
404
  />
394
405
  );
@@ -399,7 +410,7 @@ const UnMemoizedAttachmentUploadPreviewList = () => {
399
410
  FileAttachmentUploadPreview,
400
411
  ImageAttachmentUploadPreview,
401
412
  VideoAttachmentUploadPreview,
402
- attachmentManager.uploadAttachment,
413
+ handleRetry,
403
414
  removeAttachments,
404
415
  soundPackageAvailable,
405
416
  ],
@@ -496,7 +507,7 @@ const UnMemoizedAttachmentUploadPreviewList = () => {
496
507
  <AttachmentPreviewCell key={attachment.localMetadata.id}>
497
508
  <AudioAttachmentUploadPreview
498
509
  attachment={attachment}
499
- handleRetry={attachmentManager.uploadAttachment}
510
+ handleRetry={handleRetry}
500
511
  removeAttachments={removeAttachments}
501
512
  />
502
513
  </AttachmentPreviewCell>
@@ -43,11 +43,8 @@ export const AudioAttachmentUploadPreview = ({
43
43
  const messageComposer = useMessageComposer();
44
44
  const isDraft = messageComposer.draftId;
45
45
  const isEditing = messageComposer.editedMessage;
46
- const assetUrl =
47
- (isDraft || isEditing
48
- ? attachment.asset_url
49
- : (attachment.localMetadata.file as FileReference).uri) ??
50
- (attachment.localMetadata.file as FileReference).uri;
46
+ const fileUri = (attachment.localMetadata.file as FileReference | undefined)?.uri;
47
+ const assetUrl = (isDraft || isEditing ? attachment.asset_url : fileUri) ?? fileUri;
51
48
 
52
49
  const finalAttachment = useMemo(
53
50
  () => ({
@@ -97,8 +97,8 @@ export const useAudioRecorder = ({
97
97
  if (sendOnComplete) {
98
98
  attachmentManager.upsertAttachments([audioFile]);
99
99
  sendMessage();
100
- } else {
101
- await attachmentManager.uploadAttachment(audioFile);
100
+ } else if (attachmentManager.isUploadEnabled) {
101
+ await attachmentManager.uploadFile(audioFile);
102
102
  }
103
103
  } catch (error) {
104
104
  console.log('Error uploading voice recording: ', error);
@@ -20,8 +20,8 @@ export const InlineUnreadIndicator = (props: InlineUnreadIndicatorProps) => {
20
20
  {unreadCount
21
21
  ? t('messageList.newMessages.text', {
22
22
  count: unreadCount,
23
- defaultValue_one: '{{count}} new message',
24
- defaultValue_other: '{{count}} new messages',
23
+ defaultValue_one: '{{count}} unread message',
24
+ defaultValue_other: '{{count}} unread messages',
25
25
  })
26
26
  : t('messageList.unreadMessages.label', 'Unread Messages')}
27
27
  </Text>
@@ -432,12 +432,8 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
432
432
  }, [disabled]);
433
433
 
434
434
  useEffect(() => {
435
- if (thread) {
436
- setAutoscrollToRecent(true);
437
- return;
438
- }
439
-
440
435
  if (!processedMessageList.length || !paginator) {
436
+ if (thread) setAutoscrollToRecent(true);
441
437
  return;
442
438
  }
443
439
 
@@ -9,8 +9,8 @@ import { InlineUnreadIndicator } from '../InlineUnreadIndicator';
9
9
 
10
10
  describe('InlineUnreadIndicator', () => {
11
11
  it.each([
12
- [1, '1 new message'],
13
- [3, '3 new messages'],
12
+ [1, '1 unread message'],
13
+ [3, '3 unread messages'],
14
14
  ])('labels %i unread messages as "%s"', async (unreadCount, label) => {
15
15
  const translators = await new Streami18n().init();
16
16
  render(
@@ -1416,6 +1416,169 @@ describe('MessageList in a thread', () => {
1416
1416
  });
1417
1417
  expect(queryAllByLabelText('Inline unread indicator')).toHaveLength(0);
1418
1418
  });
1419
+
1420
+ // A thread opened without its replies shows only its parent message until the first page lands.
1421
+ // Autoscroll has to be on by then, or the list keeps the parent in view and the replies render out
1422
+ // of view below it.
1423
+ it('autoscrolls to the replies that arrive after the thread opened empty', async () => {
1424
+ const mockedChannel = generateChannelResponse();
1425
+ const chatClient = await getTestClientWithUser({ id: 'testID' } as UserResponse);
1426
+ useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]);
1427
+ const channel = chatClient.channel('messaging', mockedChannel.channel.id);
1428
+ await channel.watch();
1429
+ const parentMessage = channel.state.formatMessage(generateMessage({ text: 'parent' }));
1430
+ const threadInstance = new Thread({ channel, client: chatClient, parentMessage });
1431
+ // The replies arrive from the test below, not from a query.
1432
+ jest.spyOn(threadInstance, 'reload').mockResolvedValue(undefined);
1433
+ jest.spyOn(threadInstance.messagePaginator, 'toTail').mockResolvedValue(undefined);
1434
+ const { getByTestId, getByText } = render(
1435
+ <OverlayProvider>
1436
+ <Chat client={chatClient}>
1437
+ <ThreadComponent thread={threadInstance}>
1438
+ <MessageList />
1439
+ </ThreadComponent>
1440
+ </Chat>
1441
+ </OverlayProvider>,
1442
+ );
1443
+ const autoscrollThreshold = () =>
1444
+ getByTestId('message-flat-list').props.maintainVisibleContentPosition
1445
+ ?.autoscrollToTopThreshold;
1446
+ await waitFor(() => expect(autoscrollThreshold()).toBeDefined());
1447
+
1448
+ const replies = Array.from({ length: 3 }, (_, i) =>
1449
+ channel.state.formatMessage(
1450
+ generateMessage({ parent_id: parentMessage.id, text: `reply-${i}` }),
1451
+ ),
1452
+ ) as LocalMessage[];
1453
+ act(() =>
1454
+ threadInstance.messagePaginator.setItems({
1455
+ valueOrFactory: replies,
1456
+ isFirstPage: true,
1457
+ isLastPage: true,
1458
+ }),
1459
+ );
1460
+
1461
+ await waitFor(() => expect(getByText('reply-2')).toBeTruthy());
1462
+ expect(autoscrollThreshold()).toBeDefined();
1463
+ });
1464
+
1465
+ it.each([
1466
+ ['scrolls to', 'testID', 1],
1467
+ ['does not scroll to', 'other-user', 0],
1468
+ ])(
1469
+ '%s the latest reply when a new reply is the user’s own (%s)',
1470
+ async (_, userId, expectedScrolls) => {
1471
+ const { channel, chatClient, getByText, threadInstance } = await setup();
1472
+ const scrollToOffset = jest.spyOn(FlatList.prototype, 'scrollToOffset');
1473
+
1474
+ const reply = channel.state.formatMessage(
1475
+ generateMessage({
1476
+ cid: channel.cid,
1477
+ parent_id: threadInstance.id,
1478
+ text: 'new reply',
1479
+ timestamp: new Date(),
1480
+ user: generateUser({ id: userId }),
1481
+ }),
1482
+ ) as LocalMessage;
1483
+ await act(async () => {
1484
+ threadInstance.messagePaginator.ingestItem(reply);
1485
+ await new Promise((resolve) => setTimeout(resolve, 0));
1486
+ });
1487
+
1488
+ await waitFor(() => expect(getByText('new reply')).toBeTruthy());
1489
+ expect(chatClient.userId).toBe('testID');
1490
+ expect(scrollToOffset).toHaveBeenCalledTimes(expectedScrolls);
1491
+ if (expectedScrolls) {
1492
+ expect(scrollToOffset).toHaveBeenCalledWith({ animated: true, offset: 0 });
1493
+ }
1494
+ },
1495
+ );
1496
+ });
1497
+
1498
+ // Pressing a quote goes to the quoted message. A quoted reply that lives only in another thread is
1499
+ // not in this list, so its thread is opened at it instead.
1500
+ describe('MessageList quote press', () => {
1501
+ afterEach(() => {
1502
+ cleanup();
1503
+ jest.restoreAllMocks();
1504
+ });
1505
+
1506
+ const setup = async ({ quoteThreadOnlyReply }: { quoteThreadOnlyReply: boolean }) => {
1507
+ const user = generateUser();
1508
+ const base = new Date('2020-01-01T00:00:00.000Z').getTime();
1509
+ const parentMessage = generateMessage({ timestamp: new Date(base), user });
1510
+ const quotedMessage = generateMessage({
1511
+ parent_id: quoteThreadOnlyReply ? parentMessage.id : undefined,
1512
+ text: 'quoted text',
1513
+ timestamp: new Date(base + 1000),
1514
+ user,
1515
+ });
1516
+ const mockedChannel = generateChannelResponse({
1517
+ members: [generateMember({ user })],
1518
+ messages: [
1519
+ parentMessage,
1520
+ generateMessage({
1521
+ quoted_message: quotedMessage,
1522
+ quoted_message_id: quotedMessage.id,
1523
+ timestamp: new Date(base + 2000),
1524
+ user,
1525
+ }),
1526
+ ],
1527
+ });
1528
+ const chatClient = await getTestClientWithUser({ id: 'testID' } as UserResponse);
1529
+ useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]);
1530
+ const channel = chatClient.channel('messaging', mockedChannel.channel.id);
1531
+ await channel.watch();
1532
+ const thread = chatClient.threads.ensure({
1533
+ channel,
1534
+ parentMessage: channel.state.formatMessage(parentMessage),
1535
+ });
1536
+ const jumpToMessage = jest
1537
+ .spyOn(channel.messagePaginator, 'jumpToMessage')
1538
+ .mockResolvedValue(true);
1539
+ const onAlsoSentToChannelHeaderPress = jest.fn();
1540
+
1541
+ const utils = render(
1542
+ <OverlayProvider>
1543
+ <Chat client={chatClient}>
1544
+ <Channel
1545
+ channel={channel}
1546
+ onAlsoSentToChannelHeaderPress={onAlsoSentToChannelHeaderPress}
1547
+ >
1548
+ <MessageList />
1549
+ </Channel>
1550
+ </Chat>
1551
+ </OverlayProvider>,
1552
+ );
1553
+ return { ...utils, jumpToMessage, onAlsoSentToChannelHeaderPress, quotedMessage, thread };
1554
+ };
1555
+
1556
+ it('opens the thread of a quoted reply that lives only in that thread', async () => {
1557
+ const { findByText, jumpToMessage, onAlsoSentToChannelHeaderPress, quotedMessage, thread } =
1558
+ await setup({ quoteThreadOnlyReply: true });
1559
+
1560
+ fireEvent.press(await findByText('quoted text'));
1561
+
1562
+ await waitFor(() =>
1563
+ expect(onAlsoSentToChannelHeaderPress).toHaveBeenCalledWith({
1564
+ targetedMessageId: quotedMessage.id,
1565
+ thread,
1566
+ }),
1567
+ );
1568
+ expect(jumpToMessage).not.toHaveBeenCalled();
1569
+ });
1570
+
1571
+ it('jumps to a quoted message that is in the channel', async () => {
1572
+ const { findByText, jumpToMessage, onAlsoSentToChannelHeaderPress, quotedMessage } =
1573
+ await setup({ quoteThreadOnlyReply: false });
1574
+
1575
+ fireEvent.press(await findByText('quoted text'));
1576
+
1577
+ await waitFor(() =>
1578
+ expect(jumpToMessage).toHaveBeenCalledWith(quotedMessage.id, expect.anything()),
1579
+ );
1580
+ expect(onAlsoSentToChannelHeaderPress).not.toHaveBeenCalled();
1581
+ });
1419
1582
  });
1420
1583
 
1421
1584
  describe('MessageList optimistic edit', () => {
@@ -2,7 +2,11 @@ import { useEffect } from 'react';
2
2
 
3
3
  import type { MessagePaginator } from 'stream-chat';
4
4
 
5
+ import type { GoToMessage } from '../../../contexts/messageListItemContext/MessageListItemContext';
6
+ import { useMessagesContext } from '../../../contexts/messagesContext/MessagesContext';
7
+ import { useThreadContext } from '../../../contexts/threadContext/ThreadContext';
5
8
  import { useStableCallback, useStateStore } from '../../../hooks';
9
+ import { useOpenThreadAtMessage } from '../../Message/hooks/useOpenThreadAtMessage';
6
10
 
7
11
  /** How long, in milliseconds, a message the list jumped to stays highlighted. */
8
12
  export const DEFAULT_HIGHLIGHT_DURATION = 3000;
@@ -23,7 +27,9 @@ const messageFocusSelector = (state: {
23
27
  * still running when the user navigates away would scroll and highlight again on return.
24
28
  *
25
29
  * @param paginator The channel's or the open thread's paginator.
26
- * @returns `focusedMessageId`, `focusToken` and `goToMessage`, which jumps to a message by ID.
30
+ * @returns `focusedMessageId`, `focusToken` and `goToMessage`, which jumps to a message by ID. A
31
+ * message passed along that is a reply living only in another thread is not in this list, so its
32
+ * thread is opened at it instead (through `onAlsoSentToChannelHeaderPress`, when set).
27
33
  */
28
34
  export const useMessageListFocus = (paginator?: MessagePaginator) => {
29
35
  const { focusedMessageId, focusToken } =
@@ -31,7 +37,21 @@ export const useMessageListFocus = (paginator?: MessagePaginator) => {
31
37
 
32
38
  useEffect(() => () => paginator?.clearMessageFocusSignal(), [paginator]);
33
39
 
34
- const goToMessage = useStableCallback(async (messageId: string) => {
40
+ const { onAlsoSentToChannelHeaderPress } = useMessagesContext();
41
+ const { thread } = useThreadContext();
42
+ const openThreadAtMessage = useOpenThreadAtMessage();
43
+
44
+ const goToMessage: GoToMessage = useStableCallback(async (messageId, message) => {
45
+ const parentId = message?.parent_id;
46
+ if (
47
+ onAlsoSentToChannelHeaderPress &&
48
+ parentId &&
49
+ parentId !== thread?.id &&
50
+ !message.show_in_channel
51
+ ) {
52
+ await openThreadAtMessage({ parentId, targetedMessageId: messageId });
53
+ return;
54
+ }
35
55
  await paginator?.jumpToMessage(messageId, {
36
56
  focusReason: 'jump-to-message',
37
57
  focusSignalTtlMs: DEFAULT_HIGHLIGHT_DURATION,
@@ -8,6 +8,7 @@ import { PollUIStateProvider } from './contexts/PollUIStateContext';
8
8
 
9
9
  import { useIsPollCreatedByCurrentUser } from './hook/useIsPollCreatedByCurrentUser';
10
10
  import { usePollState } from './hooks/usePollState';
11
+ import { usePollTextTranslation } from './hooks/usePollTextTranslation';
11
12
 
12
13
  import {
13
14
  PollContextProvider,
@@ -27,7 +28,8 @@ export type PollContentProps = Record<string, never>;
27
28
  export const PollHeader = () => {
28
29
  const styles = useStyles();
29
30
  const { t } = useTranslationContext();
30
- const { enforceUniqueVote, isClosed, maxVotesAllowed, name } = usePollState();
31
+ const { enforceUniqueVote, isClosed, maxVotesAllowed, name, nameI18n } = usePollState();
32
+ const translate = usePollTextTranslation();
31
33
 
32
34
  const subtitle = useMemo(() => {
33
35
  if (isClosed) {
@@ -56,7 +58,7 @@ export const PollHeader = () => {
56
58
 
57
59
  return (
58
60
  <View accessible accessibilityRole='text' style={styles.headerContainer}>
59
- <Text style={[styles.headerTitle, header.title]}>{name}</Text>
61
+ <Text style={[styles.headerTitle, header.title]}>{translate(name, nameI18n)}</Text>
60
62
  <Text style={[styles.headerSubtitle, header.subtitle]}>{subtitle}</Text>
61
63
  </View>
62
64
  );
@@ -0,0 +1,152 @@
1
+ import React from 'react';
2
+
3
+ import { act, render, screen } from '@testing-library/react-native';
4
+ import dayjs from 'dayjs';
5
+ import { Poll, PollResponseData, PollVoteResponseData, StreamChat } from 'stream-chat';
6
+
7
+ import {
8
+ ChatProvider,
9
+ OwnCapabilitiesProvider,
10
+ PollContextProvider,
11
+ ThemeProvider,
12
+ TranslationProvider,
13
+ } from '../../../contexts';
14
+ import { generateMessage } from '../../../mock-builders/generator/message';
15
+ import { PollAnswerListItem } from '../components/PollAnswersList';
16
+ import { PollAllOptionsContent, PollOption } from '../components/PollOption';
17
+ import { PollResultsContent } from '../components/PollResults/PollResults';
18
+ import { PollHeader } from '../Poll';
19
+
20
+ const POLL_ID = 'poll-id';
21
+
22
+ const pollResponse = {
23
+ created_by: { id: 'other-user' },
24
+ enforce_unique_vote: true,
25
+ id: POLL_ID,
26
+ latest_answers: [],
27
+ latest_votes_by_option: {},
28
+ name: 'Favourite colour?',
29
+ name_i18n: { es_text: '¿Color favorito?', language: 'en' },
30
+ options: [
31
+ { id: 'option-red', text: 'Red', text_i18n: { es_text: 'Rojo', language: 'en' } },
32
+ { id: 'option-blue', text: 'Blue' },
33
+ ],
34
+ vote_count: 0,
35
+ vote_counts_by_option: {},
36
+ } as unknown as PollResponseData;
37
+
38
+ const answer = {
39
+ answer_text: 'Green, actually',
40
+ answer_text_i18n: { es_text: 'Verde, en realidad', language: 'en' },
41
+ created_at: new Date('2026-10-27T10:00:00.000Z').getTime(),
42
+ id: 'answer-id',
43
+ is_answer: true,
44
+ poll_id: POLL_ID,
45
+ updated_at: new Date('2026-10-27T10:00:00.000Z').getTime(),
46
+ user: { id: 'other-user', name: 'Other' },
47
+ } as unknown as PollVoteResponseData;
48
+
49
+ const setup = (userLanguage: string) => {
50
+ const client = { user: { id: 'own-user' }, userID: 'own-user' } as unknown as StreamChat;
51
+ const poll = new Poll({ client, poll: pollResponse });
52
+ const [red, blue] = poll.data.options;
53
+
54
+ render(
55
+ <ThemeProvider>
56
+ <ChatProvider value={{ client } as never}>
57
+ <TranslationProvider
58
+ value={{ t: (key: string) => key, tDateTimeParser: dayjs, userLanguage } as never}
59
+ >
60
+ <OwnCapabilitiesProvider value={{ castPollVote: true } as never}>
61
+ <PollContextProvider value={{ message: generateMessage(), poll }}>
62
+ <PollHeader />
63
+ <PollOption option={red} />
64
+ <PollOption option={blue} />
65
+ <PollAnswerListItem answer={answer} />
66
+ </PollContextProvider>
67
+ </OwnCapabilitiesProvider>
68
+ </TranslationProvider>
69
+ </ChatProvider>
70
+ </ThemeProvider>,
71
+ );
72
+
73
+ return { poll };
74
+ };
75
+
76
+ const renderDialogs = (userLanguage: string) => {
77
+ const client = { user: { id: 'own-user' }, userID: 'own-user' } as unknown as StreamChat;
78
+ const poll = new Poll({ client, poll: pollResponse });
79
+
80
+ return render(
81
+ <ThemeProvider>
82
+ <ChatProvider value={{ client } as never}>
83
+ <TranslationProvider value={{ t: (key: string) => key, userLanguage } as never}>
84
+ <OwnCapabilitiesProvider value={{} as never}>
85
+ <PollContextProvider value={{ message: generateMessage(), poll }}>
86
+ <PollResultsContent />
87
+ <PollAllOptionsContent />
88
+ </PollContextProvider>
89
+ </OwnCapabilitiesProvider>
90
+ </TranslationProvider>
91
+ </ChatProvider>
92
+ </ThemeProvider>,
93
+ );
94
+ };
95
+
96
+ describe('Poll translations', () => {
97
+ it("renders the question, options and answers in the user's language", () => {
98
+ setup('es');
99
+
100
+ expect(screen.getByText('¿Color favorito?')).toBeTruthy();
101
+ expect(screen.getByText('Rojo')).toBeTruthy();
102
+ expect(screen.getByLabelText('Rojo')).toBeTruthy();
103
+ expect(screen.getByText('Verde, en realidad')).toBeTruthy();
104
+ expect(screen.queryByText('Favourite colour?')).toBeNull();
105
+ expect(screen.queryByText('Red')).toBeNull();
106
+ });
107
+
108
+ it('falls back to the original text when a text has no translation', () => {
109
+ setup('es');
110
+
111
+ expect(screen.getByText('Blue')).toBeTruthy();
112
+ expect(screen.getByLabelText('Blue')).toBeTruthy();
113
+ });
114
+
115
+ it("renders the original texts when nothing is translated into the user's language", () => {
116
+ setup('de');
117
+
118
+ expect(screen.getByText('Favourite colour?')).toBeTruthy();
119
+ expect(screen.getByText('Red')).toBeTruthy();
120
+ expect(screen.getByText('Blue')).toBeTruthy();
121
+ expect(screen.getByText('Green, actually')).toBeTruthy();
122
+ });
123
+
124
+ it('shows a translation that arrives later through poll.updated', () => {
125
+ const { poll } = setup('de');
126
+ expect(screen.getByText('Favourite colour?')).toBeTruthy();
127
+
128
+ act(() => {
129
+ poll.handlePollUpdated({
130
+ created_at: '2026-10-27T11:00:00.000Z',
131
+ poll: {
132
+ ...pollResponse,
133
+ name_i18n: { de_text: 'Lieblingsfarbe?', language: 'en' },
134
+ },
135
+ type: 'poll.updated',
136
+ } as never);
137
+ });
138
+
139
+ expect(screen.getByText('Lieblingsfarbe?')).toBeTruthy();
140
+ expect(screen.queryByText('Favourite colour?')).toBeNull();
141
+ });
142
+
143
+ it('translates the question and options in the results and all-options dialogs', () => {
144
+ renderDialogs('es');
145
+
146
+ // Each dialog shows the question once and every option once.
147
+ expect(screen.getAllByText('¿Color favorito?')).toHaveLength(2);
148
+ expect(screen.getAllByText('Rojo')).toHaveLength(2);
149
+ expect(screen.getAllByText('Blue')).toHaveLength(2);
150
+ expect(screen.queryByText('Favourite colour?')).toBeNull();
151
+ });
152
+ });
@@ -21,6 +21,7 @@ import { Button } from '../../ui';
21
21
  import { UserAvatar } from '../../ui/Avatar/UserAvatar';
22
22
  import { usePollAnswersPagination } from '../hooks/usePollAnswersPagination';
23
23
  import { usePollState } from '../hooks/usePollState';
24
+ import { usePollTextTranslation } from '../hooks/usePollTextTranslation';
24
25
 
25
26
  export const AnswerListAddCommentButton = (props: PollButtonProps) => {
26
27
  const { t } = useTranslationContext();
@@ -75,6 +76,7 @@ export const PollAnswerListItem = ({ answer }: { answer: PollVoteResponseData })
75
76
  const { client } = useChatContext();
76
77
  const { t, tDateTimeParser } = useTranslationContext();
77
78
  const { votingVisibility } = usePollState();
79
+ const translate = usePollTextTranslation();
78
80
 
79
81
  const {
80
82
  theme: {
@@ -108,7 +110,9 @@ export const PollAnswerListItem = ({ answer }: { answer: PollVoteResponseData })
108
110
  return (
109
111
  <View style={[styles.listItemWrapper, itemStyle.wrapper]}>
110
112
  <View style={[styles.listItemContainer, itemStyle.container]}>
111
- <Text style={[styles.listItemAnswerText, itemStyle.answerText]}>{answer.answer_text}</Text>
113
+ <Text style={[styles.listItemAnswerText, itemStyle.answerText]}>
114
+ {translate(answer.answer_text ?? '', answer.answer_text_i18n)}
115
+ </Text>
112
116
  <View style={[styles.listItemInfoContainer, itemStyle.infoContainer]}>
113
117
  <View style={[styles.listItemUserInfoContainer, itemStyle.userInfoContainer]}>
114
118
  {!isAnonymous && answer.user?.image ? (
@@ -27,6 +27,7 @@ import { ProgressBar } from '../../ProgressControl/ProgressBar';
27
27
  import { UserAvatarStack } from '../../ui/Avatar/AvatarStack';
28
28
  import { useIsPollCreatedByCurrentUser } from '../hook/useIsPollCreatedByCurrentUser';
29
29
  import { usePollState } from '../hooks/usePollState';
30
+ import { usePollTextTranslation } from '../hooks/usePollTextTranslation';
30
31
  import { usePollVoteToggle } from '../hooks/usePollVoteToggle';
31
32
 
32
33
  const pollVoteAccessibilityStates = {
@@ -48,7 +49,8 @@ export const PollAllOptionsContent = ({
48
49
  additionalScrollViewProps,
49
50
  }: Pick<PollAllOptionsContentProps, 'additionalScrollViewProps'>) => {
50
51
  const { t } = useTranslationContext();
51
- const { name, options } = usePollState();
52
+ const { name, nameI18n, options } = usePollState();
53
+ const translate = usePollTextTranslation();
52
54
 
53
55
  const {
54
56
  theme: {
@@ -67,7 +69,7 @@ export const PollAllOptionsContent = ({
67
69
  >
68
70
  <View style={[styles.allOptionsTitleContainer, titleContainer]}>
69
71
  <Text style={styles.allOptionsTitleMeta}>{t('poll.question.label', 'Question')}</Text>
70
- <Text style={[styles.allOptionsTitleText, titleText]}>{name}</Text>
72
+ <Text style={[styles.allOptionsTitleText, titleText]}>{translate(name, nameI18n)}</Text>
71
73
  </View>
72
74
  <View style={[styles.allOptionsListContainer, listContainer]}>
73
75
  {options?.map((option: PollOptionClass) => (
@@ -96,6 +98,7 @@ export const PollAllOptions = ({
96
98
  export const PollOption = ({ option, showProgressBar = true, forceIncoming }: PollOptionProps) => {
97
99
  const { latestVotesByOption, voteCountsByOption, voteCount } = usePollState();
98
100
  const { t } = useTranslationContext();
101
+ const translate = usePollTextTranslation();
99
102
  const isPollCreatedByClient = useIsPollCreatedByCurrentUser();
100
103
  // `forceIncoming` marks the full-options surface, a neutral card rather than a bubble, so it wins
101
104
  // over poll ownership.
@@ -133,7 +136,7 @@ export const PollOption = ({ option, showProgressBar = true, forceIncoming }: Po
133
136
  <View style={[styles.info, info]}>
134
137
  <View style={[styles.header, header]}>
135
138
  <Text ellipsizeMode='tail' numberOfLines={2} style={[styles.text, text]}>
136
- {option.text}
139
+ {translate(option.text, option.text_i18n)}
137
140
  </Text>
138
141
  <View style={[styles.votesContainer, votesContainer]}>
139
142
  {relevantVotes.length > 0 ? (
@@ -196,6 +199,7 @@ export const VoteButton = ({ forceIncoming, onPress, option }: PollVoteButtonPro
196
199
  } = useTheme();
197
200
 
198
201
  const toggleVote = usePollVoteToggle();
202
+ const translate = usePollTextTranslation();
199
203
 
200
204
  const onPressHandler = useCallback(() => {
201
205
  if (onPress) {
@@ -213,7 +217,7 @@ export const VoteButton = ({ forceIncoming, onPress, option }: PollVoteButtonPro
213
217
 
214
218
  return ownCapabilities.castPollVote && !isClosed ? (
215
219
  <Pressable
216
- accessibilityLabel={option.text}
220
+ accessibilityLabel={translate(option.text, option.text_i18n)}
217
221
  accessibilityRole={poll.data?.enforce_unique_vote ? 'radio' : 'checkbox'}
218
222
  accessibilityState={accessibilityState}
219
223
  onPress={onPressHandler}