@planningcenter/chat-react-native 3.38.0-rc.8 → 3.38.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 (154) hide show
  1. package/build/components/conversation/jump_to_bottom_button.d.ts +1 -2
  2. package/build/components/conversation/jump_to_bottom_button.d.ts.map +1 -1
  3. package/build/components/conversation/jump_to_bottom_button.js +7 -39
  4. package/build/components/conversation/jump_to_bottom_button.js.map +1 -1
  5. package/build/components/conversation/reply_shadow_message.d.ts +2 -1
  6. package/build/components/conversation/reply_shadow_message.d.ts.map +1 -1
  7. package/build/components/conversation/reply_shadow_message.js.map +1 -1
  8. package/build/contexts/conversation_context.d.ts +1 -8
  9. package/build/contexts/conversation_context.d.ts.map +1 -1
  10. package/build/contexts/conversation_context.js +3 -21
  11. package/build/contexts/conversation_context.js.map +1 -1
  12. package/build/hooks/groups/use_group_chat_conversation_payload.d.ts.map +1 -1
  13. package/build/hooks/groups/use_group_chat_conversation_payload.js +1 -0
  14. package/build/hooks/groups/use_group_chat_conversation_payload.js.map +1 -1
  15. package/build/hooks/use_conversation_messages.d.ts +6 -15
  16. package/build/hooks/use_conversation_messages.d.ts.map +1 -1
  17. package/build/hooks/use_conversation_messages.js +9 -62
  18. package/build/hooks/use_conversation_messages.js.map +1 -1
  19. package/build/hooks/use_conversation_messages_jolt_events.d.ts.map +1 -1
  20. package/build/hooks/use_conversation_messages_jolt_events.js +4 -4
  21. package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
  22. package/build/hooks/use_conversations_actions.d.ts +0 -5
  23. package/build/hooks/use_conversations_actions.d.ts.map +1 -1
  24. package/build/hooks/use_conversations_actions.js +0 -12
  25. package/build/hooks/use_conversations_actions.js.map +1 -1
  26. package/build/hooks/use_features.d.ts +0 -1
  27. package/build/hooks/use_features.d.ts.map +1 -1
  28. package/build/hooks/use_features.js +0 -1
  29. package/build/hooks/use_features.js.map +1 -1
  30. package/build/hooks/use_mark_latest_message_read.d.ts +1 -1
  31. package/build/hooks/use_mark_latest_message_read.d.ts.map +1 -1
  32. package/build/hooks/use_mark_latest_message_read.js +1 -17
  33. package/build/hooks/use_mark_latest_message_read.js.map +1 -1
  34. package/build/hooks/use_suspense_api.d.ts +0 -1
  35. package/build/hooks/use_suspense_api.d.ts.map +1 -1
  36. package/build/hooks/use_suspense_api.js +1 -1
  37. package/build/hooks/use_suspense_api.js.map +1 -1
  38. package/build/screens/conversation_filter_recipients/components/header_row.d.ts.map +1 -1
  39. package/build/screens/conversation_filter_recipients/components/header_row.js +3 -2
  40. package/build/screens/conversation_filter_recipients/components/header_row.js.map +1 -1
  41. package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.d.ts.map +1 -1
  42. package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.js +47 -18
  43. package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.js.map +1 -1
  44. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts +2 -1
  45. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts.map +1 -1
  46. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js +23 -26
  47. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js.map +1 -1
  48. package/build/screens/conversation_filter_recipients/types.d.ts +1 -1
  49. package/build/screens/conversation_filter_recipients/types.d.ts.map +1 -1
  50. package/build/screens/conversation_filter_recipients/types.js.map +1 -1
  51. package/build/screens/conversation_screen.d.ts +0 -1
  52. package/build/screens/conversation_screen.d.ts.map +1 -1
  53. package/build/screens/conversation_screen.js +48 -95
  54. package/build/screens/conversation_screen.js.map +1 -1
  55. package/build/screens/conversation_select_recipients/components/recipient_link_row.d.ts +1 -1
  56. package/build/screens/conversation_select_recipients/components/recipient_link_row.d.ts.map +1 -1
  57. package/build/screens/conversation_select_recipients/components/recipient_link_row.js +3 -3
  58. package/build/screens/conversation_select_recipients/components/recipient_link_row.js.map +1 -1
  59. package/build/screens/conversation_select_recipients/components/team_recipient_row.d.ts.map +1 -1
  60. package/build/screens/conversation_select_recipients/components/team_recipient_row.js +1 -1
  61. package/build/screens/conversation_select_recipients/components/team_recipient_row.js.map +1 -1
  62. package/build/utils/cache/messages_cache.d.ts +0 -1
  63. package/build/utils/cache/messages_cache.d.ts.map +1 -1
  64. package/build/utils/cache/messages_cache.js +0 -4
  65. package/build/utils/cache/messages_cache.js.map +1 -1
  66. package/build/utils/group_messages.d.ts +2 -9
  67. package/build/utils/group_messages.d.ts.map +1 -1
  68. package/build/utils/group_messages.js +1 -20
  69. package/build/utils/group_messages.js.map +1 -1
  70. package/package.json +3 -3
  71. package/src/__tests__/hooks/use_group_chat_conversation_payload.test.tsx +50 -0
  72. package/src/components/conversation/jump_to_bottom_button.tsx +8 -57
  73. package/src/components/conversation/reply_shadow_message.tsx +1 -1
  74. package/src/contexts/conversation_context.tsx +2 -30
  75. package/src/hooks/groups/use_group_chat_conversation_payload.ts +1 -0
  76. package/src/hooks/use_conversation_messages.ts +20 -120
  77. package/src/hooks/use_conversation_messages_jolt_events.ts +3 -4
  78. package/src/hooks/use_conversations_actions.ts +0 -15
  79. package/src/hooks/use_features.ts +0 -1
  80. package/src/hooks/use_mark_latest_message_read.ts +2 -16
  81. package/src/hooks/use_suspense_api.ts +1 -1
  82. package/src/screens/conversation_filter_recipients/components/header_row.tsx +3 -2
  83. package/src/screens/conversation_filter_recipients/hooks/__tests__/use_service_types_with_teams.test.ts +108 -0
  84. package/src/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.tsx +46 -19
  85. package/src/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.ts +31 -29
  86. package/src/screens/conversation_filter_recipients/types.tsx +1 -1
  87. package/src/screens/conversation_screen.tsx +76 -184
  88. package/src/screens/conversation_select_recipients/components/recipient_link_row.tsx +6 -4
  89. package/src/screens/conversation_select_recipients/components/team_recipient_row.tsx +2 -1
  90. package/src/utils/__tests__/group_messages.test.ts +0 -71
  91. package/src/utils/cache/messages_cache.ts +0 -5
  92. package/src/utils/group_messages.ts +2 -42
  93. package/build/components/conversation/unread_divider.d.ts +0 -6
  94. package/build/components/conversation/unread_divider.d.ts.map +0 -1
  95. package/build/components/conversation/unread_divider.js +0 -59
  96. package/build/components/conversation/unread_divider.js.map +0 -1
  97. package/build/hooks/use_flat_list_viewability.d.ts +0 -20
  98. package/build/hooks/use_flat_list_viewability.d.ts.map +0 -1
  99. package/build/hooks/use_flat_list_viewability.js +0 -30
  100. package/build/hooks/use_flat_list_viewability.js.map +0 -1
  101. package/build/hooks/use_jump_to_bottom_action.d.ts +0 -9
  102. package/build/hooks/use_jump_to_bottom_action.d.ts.map +0 -1
  103. package/build/hooks/use_jump_to_bottom_action.js +0 -62
  104. package/build/hooks/use_jump_to_bottom_action.js.map +0 -1
  105. package/build/hooks/use_jump_to_unread_anchor.d.ts +0 -20
  106. package/build/hooks/use_jump_to_unread_anchor.d.ts.map +0 -1
  107. package/build/hooks/use_jump_to_unread_anchor.js +0 -53
  108. package/build/hooks/use_jump_to_unread_anchor.js.map +0 -1
  109. package/build/hooks/use_jump_to_unread_gates.d.ts +0 -5
  110. package/build/hooks/use_jump_to_unread_gates.d.ts.map +0 -1
  111. package/build/hooks/use_jump_to_unread_gates.js +0 -10
  112. package/build/hooks/use_jump_to_unread_gates.js.map +0 -1
  113. package/build/hooks/use_scroll_tracking.d.ts +0 -13
  114. package/build/hooks/use_scroll_tracking.d.ts.map +0 -1
  115. package/build/hooks/use_scroll_tracking.js +0 -45
  116. package/build/hooks/use_scroll_tracking.js.map +0 -1
  117. package/build/hooks/use_track_highest_seen_message.d.ts +0 -4
  118. package/build/hooks/use_track_highest_seen_message.d.ts.map +0 -1
  119. package/build/hooks/use_track_highest_seen_message.js +0 -35
  120. package/build/hooks/use_track_highest_seen_message.js.map +0 -1
  121. package/build/utils/conversation_messages.d.ts +0 -10
  122. package/build/utils/conversation_messages.d.ts.map +0 -1
  123. package/build/utils/conversation_messages.js +0 -22
  124. package/build/utils/conversation_messages.js.map +0 -1
  125. package/build/utils/highest_seen_tracker.d.ts +0 -12
  126. package/build/utils/highest_seen_tracker.d.ts.map +0 -1
  127. package/build/utils/highest_seen_tracker.js +0 -37
  128. package/build/utils/highest_seen_tracker.js.map +0 -1
  129. package/build/utils/message_viewability.d.ts +0 -24
  130. package/build/utils/message_viewability.d.ts.map +0 -1
  131. package/build/utils/message_viewability.js +0 -29
  132. package/build/utils/message_viewability.js.map +0 -1
  133. package/build/utils/unread_divider_helpers.d.ts +0 -18
  134. package/build/utils/unread_divider_helpers.d.ts.map +0 -1
  135. package/build/utils/unread_divider_helpers.js +0 -13
  136. package/build/utils/unread_divider_helpers.js.map +0 -1
  137. package/src/__tests__/hooks/use_conversation_messages.test.tsx +0 -109
  138. package/src/__tests__/hooks/use_mark_latest_message_read.test.tsx +0 -154
  139. package/src/__tests__/utils/cache/messages_cache.test.ts +0 -54
  140. package/src/components/conversation/unread_divider.tsx +0 -90
  141. package/src/hooks/use_flat_list_viewability.ts +0 -50
  142. package/src/hooks/use_jump_to_bottom_action.ts +0 -75
  143. package/src/hooks/use_jump_to_unread_anchor.ts +0 -68
  144. package/src/hooks/use_jump_to_unread_gates.ts +0 -10
  145. package/src/hooks/use_scroll_tracking.ts +0 -64
  146. package/src/hooks/use_track_highest_seen_message.ts +0 -43
  147. package/src/utils/__tests__/conversation_messages.test.ts +0 -105
  148. package/src/utils/__tests__/highest_seen_tracker.test.ts +0 -82
  149. package/src/utils/__tests__/message_viewability.test.ts +0 -168
  150. package/src/utils/__tests__/unread_divider_helpers.test.ts +0 -85
  151. package/src/utils/conversation_messages.ts +0 -37
  152. package/src/utils/highest_seen_tracker.ts +0 -42
  153. package/src/utils/message_viewability.ts +0 -49
  154. package/src/utils/unread_divider_helpers.ts +0 -25
@@ -1,35 +0,0 @@
1
- import { useCallback, useEffect, useMemo } from 'react';
2
- import { AppState } from 'react-native';
3
- import { useConversationContext } from '../contexts/conversation_context';
4
- import { makeHighestSeenTracker } from '../utils/highest_seen_tracker';
5
- import { useConversationsMarkReadUpTo } from './use_conversations_actions';
6
- import { useJumpToUnreadGates } from './use_jump_to_unread_gates';
7
- export function useTrackHighestSeenMessage() {
8
- const { conversationId, currentPageReplyRootId } = useConversationContext();
9
- const { jumpToUnreadActive } = useJumpToUnreadGates();
10
- const enabled = jumpToUnreadActive && !currentPageReplyRootId;
11
- const { mutate: markReadUpTo } = useConversationsMarkReadUpTo({ conversationId });
12
- const tracker = useMemo(() => makeHighestSeenTracker(conversationId, ({ sortKey }) => markReadUpTo({ sortKey })), [conversationId, markReadUpTo]);
13
- const onMessageSeen = useCallback((sortKey) => {
14
- if (!enabled)
15
- return;
16
- tracker.onSeen(sortKey);
17
- }, [enabled, tracker]);
18
- useEffect(() => {
19
- if (!enabled)
20
- return;
21
- const sub = AppState.addEventListener('change', state => {
22
- if (state !== 'active')
23
- tracker.flushNow();
24
- });
25
- return () => sub.remove();
26
- }, [enabled, tracker]);
27
- useEffect(() => {
28
- return () => {
29
- tracker.flushNow();
30
- tracker.cancel();
31
- };
32
- }, [tracker]);
33
- return { onMessageSeen };
34
- }
35
- //# sourceMappingURL=use_track_highest_seen_message.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"use_track_highest_seen_message.js","sourceRoot":"","sources":["../../src/hooks/use_track_highest_seen_message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AACtE,OAAO,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAA;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAEjE,MAAM,UAAU,0BAA0B;IACxC,MAAM,EAAE,cAAc,EAAE,sBAAsB,EAAE,GAAG,sBAAsB,EAAE,CAAA;IAC3E,MAAM,EAAE,kBAAkB,EAAE,GAAG,oBAAoB,EAAE,CAAA;IACrD,MAAM,OAAO,GAAG,kBAAkB,IAAI,CAAC,sBAAsB,CAAA;IAC7D,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,4BAA4B,CAAC,EAAE,cAAc,EAAE,CAAC,CAAA;IAEjF,MAAM,OAAO,GAAG,OAAO,CACrB,GAAG,EAAE,CAAC,sBAAsB,CAAC,cAAc,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,EACxF,CAAC,cAAc,EAAE,YAAY,CAAC,CAC/B,CAAA;IAED,MAAM,aAAa,GAAG,WAAW,CAC/B,CAAC,OAAe,EAAE,EAAE;QAClB,IAAI,CAAC,OAAO;YAAE,OAAM;QACpB,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACzB,CAAC,EACD,CAAC,OAAO,EAAE,OAAO,CAAC,CACnB,CAAA;IAED,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO;YAAE,OAAM;QACpB,MAAM,GAAG,GAAG,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE;YACtD,IAAI,KAAK,KAAK,QAAQ;gBAAE,OAAO,CAAC,QAAQ,EAAE,CAAA;QAC5C,CAAC,CAAC,CAAA;QACF,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,CAAA;IAC3B,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;IAEtB,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE;YACV,OAAO,CAAC,QAAQ,EAAE,CAAA;YAClB,OAAO,CAAC,MAAM,EAAE,CAAA;QAClB,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IAEb,OAAO,EAAE,aAAa,EAAE,CAAA;AAC1B,CAAC","sourcesContent":["import { useCallback, useEffect, useMemo } from 'react'\nimport { AppState } from 'react-native'\nimport { useConversationContext } from '../contexts/conversation_context'\nimport { makeHighestSeenTracker } from '../utils/highest_seen_tracker'\nimport { useConversationsMarkReadUpTo } from './use_conversations_actions'\nimport { useJumpToUnreadGates } from './use_jump_to_unread_gates'\n\nexport function useTrackHighestSeenMessage() {\n const { conversationId, currentPageReplyRootId } = useConversationContext()\n const { jumpToUnreadActive } = useJumpToUnreadGates()\n const enabled = jumpToUnreadActive && !currentPageReplyRootId\n const { mutate: markReadUpTo } = useConversationsMarkReadUpTo({ conversationId })\n\n const tracker = useMemo(\n () => makeHighestSeenTracker(conversationId, ({ sortKey }) => markReadUpTo({ sortKey })),\n [conversationId, markReadUpTo]\n )\n\n const onMessageSeen = useCallback(\n (sortKey: string) => {\n if (!enabled) return\n tracker.onSeen(sortKey)\n },\n [enabled, tracker]\n )\n\n useEffect(() => {\n if (!enabled) return\n const sub = AppState.addEventListener('change', state => {\n if (state !== 'active') tracker.flushNow()\n })\n return () => sub.remove()\n }, [enabled, tracker])\n\n useEffect(() => {\n return () => {\n tracker.flushNow()\n tracker.cancel()\n }\n }, [tracker])\n\n return { onMessageSeen }\n}\n"]}
@@ -1,10 +0,0 @@
1
- import { ApiCollection, MessageResource } from '../types';
2
- import { RequestData } from './client';
3
- export type MessagesPageParam = Partial<RequestData> & {
4
- order?: 'asc' | 'desc';
5
- };
6
- export declare const anchoredSeedPageParams: (anchor: string) => MessagesPageParam[];
7
- export declare const olderPageParam: (page: ApiCollection<MessageResource>) => MessagesPageParam | undefined;
8
- export declare const newerPageParam: (page: ApiCollection<MessageResource>) => MessagesPageParam | undefined;
9
- export declare const sortAndFilterMessages: (pages: ApiCollection<MessageResource>[]) => MessageResource[];
10
- //# sourceMappingURL=conversation_messages.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"conversation_messages.d.ts","sourceRoot":"","sources":["../../src/utils/conversation_messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAEtC,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG;IACrD,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;CACvB,CAAA;AAED,eAAO,MAAM,sBAAsB,GAAI,QAAQ,MAAM,KAAG,iBAAiB,EAGxE,CAAA;AAED,eAAO,MAAM,cAAc,GACzB,MAAM,aAAa,CAAC,eAAe,CAAC,KACnC,iBAAiB,GAAG,SAItB,CAAA;AAED,eAAO,MAAM,cAAc,GACzB,MAAM,aAAa,CAAC,eAAe,CAAC,KACnC,iBAAiB,GAAG,SAItB,CAAA;AAED,eAAO,MAAM,qBAAqB,GAAI,OAAO,aAAa,CAAC,eAAe,CAAC,EAAE,KAAG,eAAe,EAQjD,CAAA"}
@@ -1,22 +0,0 @@
1
- export const anchoredSeedPageParams = (anchor) => [
2
- { where: { id_gte: anchor }, order: 'asc' },
3
- { where: { id_lt: anchor }, order: 'desc' },
4
- ];
5
- export const olderPageParam = (page) => {
6
- const idLt = page.meta?.next?.idLt;
7
- if (!idLt)
8
- return undefined;
9
- return { where: { id_lt: idLt }, order: 'desc' };
10
- };
11
- export const newerPageParam = (page) => {
12
- const idGt = page.meta?.next?.idGt;
13
- if (!idGt)
14
- return undefined;
15
- return { where: { id_gt: idGt }, order: 'asc' };
16
- };
17
- export const sortAndFilterMessages = (pages) => pages
18
- .flatMap(page => page.data)
19
- .filter(message => (!message.deletedAt || message.replyRootId) &&
20
- (message.attachments?.length || message.text?.length))
21
- .sort((a, b) => -a.id.localeCompare(b.id));
22
- //# sourceMappingURL=conversation_messages.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"conversation_messages.js","sourceRoot":"","sources":["../../src/utils/conversation_messages.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,MAAc,EAAuB,EAAE,CAAC;IAC7E,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;IAC3C,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;CAC5C,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,IAAoC,EACL,EAAE;IACjC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAA;IAClC,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAA;IAC3B,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AAClD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,IAAoC,EACL,EAAE;IACjC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAA;IAClC,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAA;IAC3B,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;AACjD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,KAAuC,EAAqB,EAAE,CAClG,KAAK;KACF,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;KAC1B,MAAM,CACL,OAAO,CAAC,EAAE,CACR,CAAC,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC;IAC3C,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,IAAI,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CACxD;KACA,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA","sourcesContent":["import { ApiCollection, MessageResource } from '../types'\nimport { RequestData } from './client'\n\nexport type MessagesPageParam = Partial<RequestData> & {\n order?: 'asc' | 'desc'\n}\n\nexport const anchoredSeedPageParams = (anchor: string): MessagesPageParam[] => [\n { where: { id_gte: anchor }, order: 'asc' },\n { where: { id_lt: anchor }, order: 'desc' },\n]\n\nexport const olderPageParam = (\n page: ApiCollection<MessageResource>\n): MessagesPageParam | undefined => {\n const idLt = page.meta?.next?.idLt\n if (!idLt) return undefined\n return { where: { id_lt: idLt }, order: 'desc' }\n}\n\nexport const newerPageParam = (\n page: ApiCollection<MessageResource>\n): MessagesPageParam | undefined => {\n const idGt = page.meta?.next?.idGt\n if (!idGt) return undefined\n return { where: { id_gt: idGt }, order: 'asc' }\n}\n\nexport const sortAndFilterMessages = (pages: ApiCollection<MessageResource>[]): MessageResource[] =>\n pages\n .flatMap(page => page.data)\n .filter(\n message =>\n (!message.deletedAt || message.replyRootId) &&\n (message.attachments?.length || message.text?.length)\n )\n .sort((a, b) => -a.id.localeCompare(b.id))\n"]}
@@ -1,12 +0,0 @@
1
- export declare const FLUSH_DELAY_MS = 2000;
2
- type SendFn = (args: {
3
- conversationId: number;
4
- sortKey: string;
5
- }) => void;
6
- export declare function makeHighestSeenTracker(conversationId: number, send: SendFn, flushDelayMs?: number): {
7
- onSeen(sortKey: string): void;
8
- flushNow(): void;
9
- cancel(): void;
10
- };
11
- export {};
12
- //# sourceMappingURL=highest_seen_tracker.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"highest_seen_tracker.d.ts","sourceRoot":"","sources":["../../src/utils/highest_seen_tracker.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,OAAO,CAAA;AAElC,KAAK,MAAM,GAAG,CAAC,IAAI,EAAE;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,CAAA;AAEzE,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,YAAY,GAAE,MAAuB;oBAcnB,MAAM;;;EAoBzB"}
@@ -1,37 +0,0 @@
1
- export const FLUSH_DELAY_MS = 2000;
2
- export function makeHighestSeenTracker(conversationId, send, flushDelayMs = FLUSH_DELAY_MS) {
3
- let highest = null;
4
- let lastSent = null;
5
- let timer = null;
6
- const fire = () => {
7
- timer = null;
8
- if (!highest || highest === lastSent)
9
- return;
10
- lastSent = highest;
11
- send({ conversationId, sortKey: highest });
12
- };
13
- return {
14
- onSeen(sortKey) {
15
- if (highest && sortKey.localeCompare(highest) <= 0)
16
- return;
17
- highest = sortKey;
18
- if (timer)
19
- clearTimeout(timer);
20
- timer = setTimeout(fire, flushDelayMs);
21
- },
22
- flushNow() {
23
- if (timer) {
24
- clearTimeout(timer);
25
- timer = null;
26
- }
27
- fire();
28
- },
29
- cancel() {
30
- if (timer) {
31
- clearTimeout(timer);
32
- timer = null;
33
- }
34
- },
35
- };
36
- }
37
- //# sourceMappingURL=highest_seen_tracker.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"highest_seen_tracker.js","sourceRoot":"","sources":["../../src/utils/highest_seen_tracker.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAA;AAIlC,MAAM,UAAU,sBAAsB,CACpC,cAAsB,EACtB,IAAY,EACZ,eAAuB,cAAc;IAErC,IAAI,OAAO,GAAkB,IAAI,CAAA;IACjC,IAAI,QAAQ,GAAkB,IAAI,CAAA;IAClC,IAAI,KAAK,GAAyC,IAAI,CAAA;IAEtD,MAAM,IAAI,GAAG,GAAG,EAAE;QAChB,KAAK,GAAG,IAAI,CAAA;QACZ,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,QAAQ;YAAE,OAAM;QAC5C,QAAQ,GAAG,OAAO,CAAA;QAClB,IAAI,CAAC,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAA;IAC5C,CAAC,CAAA;IAED,OAAO;QACL,MAAM,CAAC,OAAe;YACpB,IAAI,OAAO,IAAI,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC;gBAAE,OAAM;YAC1D,OAAO,GAAG,OAAO,CAAA;YACjB,IAAI,KAAK;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAA;YAC9B,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;QACxC,CAAC;QACD,QAAQ;YACN,IAAI,KAAK,EAAE,CAAC;gBACV,YAAY,CAAC,KAAK,CAAC,CAAA;gBACnB,KAAK,GAAG,IAAI,CAAA;YACd,CAAC;YACD,IAAI,EAAE,CAAA;QACR,CAAC;QACD,MAAM;YACJ,IAAI,KAAK,EAAE,CAAC;gBACV,YAAY,CAAC,KAAK,CAAC,CAAA;gBACnB,KAAK,GAAG,IAAI,CAAA;YACd,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["export const FLUSH_DELAY_MS = 2000\n\ntype SendFn = (args: { conversationId: number; sortKey: string }) => void\n\nexport function makeHighestSeenTracker(\n conversationId: number,\n send: SendFn,\n flushDelayMs: number = FLUSH_DELAY_MS\n) {\n let highest: string | null = null\n let lastSent: string | null = null\n let timer: ReturnType<typeof setTimeout> | null = null\n\n const fire = () => {\n timer = null\n if (!highest || highest === lastSent) return\n lastSent = highest\n send({ conversationId, sortKey: highest })\n }\n\n return {\n onSeen(sortKey: string) {\n if (highest && sortKey.localeCompare(highest) <= 0) return\n highest = sortKey\n if (timer) clearTimeout(timer)\n timer = setTimeout(fire, flushDelayMs)\n },\n flushNow() {\n if (timer) {\n clearTimeout(timer)\n timer = null\n }\n fire()\n },\n cancel() {\n if (timer) {\n clearTimeout(timer)\n timer = null\n }\n },\n }\n}\n"]}
@@ -1,24 +0,0 @@
1
- export interface ViewableEntry<Item> {
2
- key: string;
3
- isViewable: boolean;
4
- item: Item;
5
- }
6
- export interface ViewabilityEvent<Item> {
7
- viewableItems: ViewableEntry<Item>[];
8
- changed: ViewableEntry<Item>[];
9
- userHasScrolled: boolean;
10
- }
11
- export type ViewabilityObserver<Item> = (event: ViewabilityEvent<Item>) => void;
12
- export declare function reportViewableMessages<Item extends {
13
- id?: string;
14
- type?: string;
15
- }>(onMessageSeen: (id: string) => void): ViewabilityObserver<Item>;
16
- export declare function detectDividerExitTowardNewer<Item extends {
17
- id?: string;
18
- type?: string;
19
- }>({ dividerKey, initialMessageId, onExited, }: {
20
- dividerKey: string;
21
- initialMessageId: string | null;
22
- onExited: () => void;
23
- }): ViewabilityObserver<Item>;
24
- //# sourceMappingURL=message_viewability.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"message_viewability.d.ts","sourceRoot":"","sources":["../../src/utils/message_viewability.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa,CAAC,IAAI;IACjC,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,OAAO,CAAA;IACnB,IAAI,EAAE,IAAI,CAAA;CACX;AAED,MAAM,WAAW,gBAAgB,CAAC,IAAI;IACpC,aAAa,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,CAAA;IACpC,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,CAAA;IAC9B,eAAe,EAAE,OAAO,CAAA;CACzB;AAED,MAAM,MAAM,mBAAmB,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,gBAAgB,CAAC,IAAI,CAAC,KAAK,IAAI,CAAA;AAE/E,wBAAgB,sBAAsB,CAAC,IAAI,SAAS;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EAChF,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAClC,mBAAmB,CAAC,IAAI,CAAC,CAS3B;AAED,wBAAgB,4BAA4B,CAAC,IAAI,SAAS;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,EACxF,UAAU,EACV,gBAAgB,EAChB,QAAQ,GACT,EAAE;IACD,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,QAAQ,EAAE,MAAM,IAAI,CAAA;CACrB,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAW5B"}
@@ -1,29 +0,0 @@
1
- import { dividerExitedTowardNewer } from './unread_divider_helpers';
2
- export function reportViewableMessages(onMessageSeen) {
3
- return ({ viewableItems, userHasScrolled }) => {
4
- if (!userHasScrolled)
5
- return;
6
- for (const entry of viewableItems) {
7
- if (entry.item?.type !== 'Message')
8
- continue;
9
- const id = entry.item?.id;
10
- if (typeof id === 'string')
11
- onMessageSeen(id);
12
- }
13
- };
14
- }
15
- export function detectDividerExitTowardNewer({ dividerKey, initialMessageId, onExited, }) {
16
- return ({ viewableItems, changed, userHasScrolled }) => {
17
- if (!userHasScrolled || !initialMessageId)
18
- return;
19
- const exited = dividerExitedTowardNewer({
20
- changed,
21
- viewableItems,
22
- dividerKey,
23
- initialMessageId,
24
- });
25
- if (exited)
26
- onExited();
27
- };
28
- }
29
- //# sourceMappingURL=message_viewability.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"message_viewability.js","sourceRoot":"","sources":["../../src/utils/message_viewability.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAA;AAgBnE,MAAM,UAAU,sBAAsB,CACpC,aAAmC;IAEnC,OAAO,CAAC,EAAE,aAAa,EAAE,eAAe,EAAE,EAAE,EAAE;QAC5C,IAAI,CAAC,eAAe;YAAE,OAAM;QAC5B,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;YAClC,IAAI,KAAK,CAAC,IAAI,EAAE,IAAI,KAAK,SAAS;gBAAE,SAAQ;YAC5C,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,EAAE,CAAA;YACzB,IAAI,OAAO,EAAE,KAAK,QAAQ;gBAAE,aAAa,CAAC,EAAE,CAAC,CAAA;QAC/C,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED,MAAM,UAAU,4BAA4B,CAA8C,EACxF,UAAU,EACV,gBAAgB,EAChB,QAAQ,GAKT;IACC,OAAO,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,EAAE,EAAE;QACrD,IAAI,CAAC,eAAe,IAAI,CAAC,gBAAgB;YAAE,OAAM;QACjD,MAAM,MAAM,GAAG,wBAAwB,CAAC;YACtC,OAAO;YACP,aAAa;YACb,UAAU;YACV,gBAAgB;SACjB,CAAC,CAAA;QACF,IAAI,MAAM;YAAE,QAAQ,EAAE,CAAA;IACxB,CAAC,CAAA;AACH,CAAC","sourcesContent":["import { dividerExitedTowardNewer } from './unread_divider_helpers'\n\nexport interface ViewableEntry<Item> {\n key: string\n isViewable: boolean\n item: Item\n}\n\nexport interface ViewabilityEvent<Item> {\n viewableItems: ViewableEntry<Item>[]\n changed: ViewableEntry<Item>[]\n userHasScrolled: boolean\n}\n\nexport type ViewabilityObserver<Item> = (event: ViewabilityEvent<Item>) => void\n\nexport function reportViewableMessages<Item extends { id?: string; type?: string }>(\n onMessageSeen: (id: string) => void\n): ViewabilityObserver<Item> {\n return ({ viewableItems, userHasScrolled }) => {\n if (!userHasScrolled) return\n for (const entry of viewableItems) {\n if (entry.item?.type !== 'Message') continue\n const id = entry.item?.id\n if (typeof id === 'string') onMessageSeen(id)\n }\n }\n}\n\nexport function detectDividerExitTowardNewer<Item extends { id?: string; type?: string }>({\n dividerKey,\n initialMessageId,\n onExited,\n}: {\n dividerKey: string\n initialMessageId: string | null\n onExited: () => void\n}): ViewabilityObserver<Item> {\n return ({ viewableItems, changed, userHasScrolled }) => {\n if (!userHasScrolled || !initialMessageId) return\n const exited = dividerExitedTowardNewer({\n changed,\n viewableItems,\n dividerKey,\n initialMessageId,\n })\n if (exited) onExited()\n }\n}\n"]}
@@ -1,18 +0,0 @@
1
- type ViewableChangeEntry = {
2
- key: string;
3
- isViewable: boolean;
4
- };
5
- type ViewableItem = {
6
- item: {
7
- id?: string;
8
- type?: string;
9
- };
10
- };
11
- export declare function dividerExitedTowardNewer({ changed, viewableItems, dividerKey, initialMessageId, }: {
12
- changed: ViewableChangeEntry[];
13
- viewableItems: ViewableItem[];
14
- dividerKey: string;
15
- initialMessageId: string;
16
- }): boolean;
17
- export {};
18
- //# sourceMappingURL=unread_divider_helpers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"unread_divider_helpers.d.ts","sourceRoot":"","sources":["../../src/utils/unread_divider_helpers.ts"],"names":[],"mappings":"AAAA,KAAK,mBAAmB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAA;AAC/D,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAA;AAE5D,wBAAgB,wBAAwB,CAAC,EACvC,OAAO,EACP,aAAa,EACb,UAAU,EACV,gBAAgB,GACjB,EAAE;IACD,OAAO,EAAE,mBAAmB,EAAE,CAAA;IAC9B,aAAa,EAAE,YAAY,EAAE,CAAA;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,EAAE,MAAM,CAAA;CACzB,GAAG,OAAO,CAWV"}
@@ -1,13 +0,0 @@
1
- export function dividerExitedTowardNewer({ changed, viewableItems, dividerKey, initialMessageId, }) {
2
- const dividerExited = changed.some(c => c.key === dividerKey && !c.isViewable);
3
- if (!dividerExited)
4
- return false;
5
- const visibleMessageIds = viewableItems
6
- .filter(v => v.item?.type === 'Message')
7
- .map(v => v.item?.id)
8
- .filter((id) => !!id);
9
- if (visibleMessageIds.length === 0)
10
- return false;
11
- return visibleMessageIds.every(id => id.localeCompare(initialMessageId) > 0);
12
- }
13
- //# sourceMappingURL=unread_divider_helpers.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"unread_divider_helpers.js","sourceRoot":"","sources":["../../src/utils/unread_divider_helpers.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,wBAAwB,CAAC,EACvC,OAAO,EACP,aAAa,EACb,UAAU,EACV,gBAAgB,GAMjB;IACC,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,UAAU,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAA;IAC9E,IAAI,CAAC,aAAa;QAAE,OAAO,KAAK,CAAA;IAEhC,MAAM,iBAAiB,GAAG,aAAa;SACpC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC;SACvC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,EAAE,EAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IACrC,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAEhD,OAAO,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,aAAa,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAA;AAC9E,CAAC","sourcesContent":["type ViewableChangeEntry = { key: string; isViewable: boolean }\ntype ViewableItem = { item: { id?: string; type?: string } }\n\nexport function dividerExitedTowardNewer({\n changed,\n viewableItems,\n dividerKey,\n initialMessageId,\n}: {\n changed: ViewableChangeEntry[]\n viewableItems: ViewableItem[]\n dividerKey: string\n initialMessageId: string\n}): boolean {\n const dividerExited = changed.some(c => c.key === dividerKey && !c.isViewable)\n if (!dividerExited) return false\n\n const visibleMessageIds = viewableItems\n .filter(v => v.item?.type === 'Message')\n .map(v => v.item?.id)\n .filter((id): id is string => !!id)\n if (visibleMessageIds.length === 0) return false\n\n return visibleMessageIds.every(id => id.localeCompare(initialMessageId) > 0)\n}\n"]}
@@ -1,109 +0,0 @@
1
- import { QueryClientProvider } from '@tanstack/react-query'
2
- import { act, renderHook } from '@testing-library/react-native'
3
- import React, { Suspense } from 'react'
4
- import { buildTestQueryClient } from '../../__utils__/query_client'
5
- import { ConversationContextProvider } from '../../contexts/conversation_context'
6
- import * as useApiClientModule from '../../hooks/use_api_client'
7
- import { useConversationMessages } from '../../hooks/use_conversation_messages'
8
- import { ApiCollection, MessageResource } from '../../types'
9
-
10
- const mockMessage = (id: string): MessageResource =>
11
- ({
12
- id,
13
- type: 'Message',
14
- text: `msg ${id}`,
15
- attachments: [],
16
- deletedAt: null,
17
- replyRootId: null,
18
- }) as MessageResource
19
-
20
- const apiResponse = (data: MessageResource[]): ApiCollection<MessageResource> => ({
21
- data,
22
- links: {},
23
- meta: { count: data.length, totalCount: data.length, next: {} },
24
- })
25
-
26
- const createWrapper = (initialMessageId: string | null) => {
27
- const queryClient = buildTestQueryClient()
28
-
29
- return ({ children }: { children: React.ReactNode }) => (
30
- <QueryClientProvider client={queryClient}>
31
- <Suspense fallback={null}>
32
- <ConversationContextProvider
33
- conversationId={123}
34
- currentPageReplyRootId={null}
35
- initialMessageId={initialMessageId}
36
- initialMessageIdIsAnchor={!!initialMessageId}
37
- >
38
- {children}
39
- </ConversationContextProvider>
40
- </Suspense>
41
- </QueryClientProvider>
42
- )
43
- }
44
-
45
- const flushPromises = async () => {
46
- await act(async () => {
47
- await Promise.resolve()
48
- await Promise.resolve()
49
- await Promise.resolve()
50
- await Promise.resolve()
51
- })
52
- }
53
-
54
- const mockApiClient = (get: jest.Mock) => {
55
- jest.spyOn(useApiClientModule, 'useApiClient').mockReturnValue({
56
- chat: { get },
57
- } as unknown as ReturnType<typeof useApiClientModule.useApiClient>)
58
- }
59
-
60
- describe('useConversationMessages', () => {
61
- afterEach(() => {
62
- jest.restoreAllMocks()
63
- })
64
-
65
- it('fires two parallel seed requests with id_gte/asc and id_lt/desc when anchored', async () => {
66
- const get = jest.fn(({ data }: { data: { where?: Record<string, string> } }) => {
67
- if (data.where?.id_gte === '01B') {
68
- return Promise.resolve(apiResponse([mockMessage('01C'), mockMessage('01B')]))
69
- }
70
- if (data.where?.id_lt === '01B') {
71
- return Promise.resolve(apiResponse([mockMessage('01A')]))
72
- }
73
- return Promise.resolve(apiResponse([]))
74
- })
75
- mockApiClient(get)
76
-
77
- renderHook(() => useConversationMessages({ conversation_id: 123 }), {
78
- wrapper: createWrapper('01B'),
79
- })
80
- await flushPromises()
81
-
82
- expect(get).toHaveBeenCalledTimes(2)
83
- const requested = get.mock.calls.map(
84
- ([req]: [{ data: { where?: Record<string, string>; order?: string } }]) => ({
85
- where: req.data.where,
86
- order: req.data.order,
87
- })
88
- )
89
- expect(requested).toEqual(
90
- expect.arrayContaining([
91
- { where: { id_gte: '01B' }, order: 'asc' },
92
- { where: { id_lt: '01B' }, order: 'desc' },
93
- ])
94
- )
95
- })
96
-
97
- it('fires one fetch with no cursor when not anchored', async () => {
98
- const get = jest.fn(() => Promise.resolve(apiResponse([mockMessage('01A')])))
99
- mockApiClient(get)
100
-
101
- renderHook(() => useConversationMessages({ conversation_id: 123 }), {
102
- wrapper: createWrapper(null),
103
- })
104
- await flushPromises()
105
-
106
- expect(get).toHaveBeenCalledTimes(1)
107
- expect(get.mock.calls[0][0].data.where).toBeUndefined()
108
- })
109
- })
@@ -1,154 +0,0 @@
1
- import { QueryClientProvider } from '@tanstack/react-query'
2
- import { renderHook } from '@testing-library/react-native'
3
- import React, { useEffect } from 'react'
4
- import { buildTestQueryClient } from '../../__utils__/query_client'
5
- import {
6
- ConversationContextProvider,
7
- useConversationContext,
8
- } from '../../contexts/conversation_context'
9
- import * as appStateModule from '../../hooks/use_app_state'
10
- import * as conversationsActionsModule from '../../hooks/use_conversations_actions'
11
- import * as featuresModule from '../../hooks/use_features'
12
- import { useMarkLatestMessageRead } from '../../hooks/use_mark_latest_message_read'
13
- import { ConversationResource } from '../../types'
14
-
15
- const conversation = {
16
- id: 1,
17
- type: 'Conversation',
18
- unreadCount: 3,
19
- unreadReactionCount: 0,
20
- conversationMembership: { lastReadMessageSortKey: '01A' },
21
- } as unknown as ConversationResource
22
-
23
- interface WrapperProps {
24
- replyRootId?: string | null
25
- initialMessageIdIsAnchor?: boolean
26
- atEndOfMessageHistory?: boolean
27
- }
28
-
29
- const createWrapper = ({
30
- replyRootId = null,
31
- initialMessageIdIsAnchor = false,
32
- atEndOfMessageHistory = !initialMessageIdIsAnchor,
33
- }: WrapperProps = {}) => {
34
- const queryClient = buildTestQueryClient()
35
- const Wrapper = ({ children }: { children: React.ReactNode }) => (
36
- <QueryClientProvider client={queryClient}>
37
- <ConversationContextProvider
38
- conversationId={1}
39
- currentPageReplyRootId={replyRootId}
40
- initialMessageId={initialMessageIdIsAnchor ? '01A' : null}
41
- initialMessageIdIsAnchor={initialMessageIdIsAnchor}
42
- >
43
- <AtEndPrimer atEndOfMessageHistory={atEndOfMessageHistory}>{children}</AtEndPrimer>
44
- </ConversationContextProvider>
45
- </QueryClientProvider>
46
- )
47
- return Wrapper
48
- }
49
-
50
- const AtEndPrimer = ({
51
- atEndOfMessageHistory,
52
- children,
53
- }: {
54
- atEndOfMessageHistory: boolean
55
- children: React.ReactNode
56
- }) => {
57
- const { setAtEndOfMessageHistory } = useConversationContext()
58
- useEffect(() => {
59
- setAtEndOfMessageHistory(atEndOfMessageHistory)
60
- }, [atEndOfMessageHistory, setAtEndOfMessageHistory])
61
- return <>{children}</>
62
- }
63
-
64
- const mockFeatures = (enabled: boolean) => {
65
- jest.spyOn(featuresModule, 'useFeatures').mockReturnValue({
66
- features: [],
67
- featureEnabled: () => enabled,
68
- })
69
- }
70
-
71
- const mockMarkRead = () => {
72
- const markRead = jest.fn()
73
- jest.spyOn(conversationsActionsModule, 'useConversationsMarkRead').mockReturnValue({
74
- markRead,
75
- read: false,
76
- } as unknown as ReturnType<typeof conversationsActionsModule.useConversationsMarkRead>)
77
- return markRead
78
- }
79
-
80
- const mockAppState = (state: string = 'active') => {
81
- jest.spyOn(appStateModule, 'useAppState').mockReturnValue(state)
82
- }
83
-
84
- describe('useMarkLatestMessageRead', () => {
85
- afterEach(() => {
86
- jest.restoreAllMocks()
87
- })
88
-
89
- it('fires markRead when JTU is off (backward compat preserved)', () => {
90
- mockAppState()
91
- mockFeatures(false)
92
- const markRead = mockMarkRead()
93
-
94
- renderHook(() => useMarkLatestMessageRead({ conversation }), {
95
- wrapper: createWrapper(),
96
- })
97
-
98
- expect(markRead).toHaveBeenCalledWith(true)
99
- })
100
-
101
- it('does NOT fire markRead when in a reply view (parity fix, ships flag-off)', () => {
102
- mockAppState()
103
- mockFeatures(false)
104
- const markRead = mockMarkRead()
105
-
106
- renderHook(() => useMarkLatestMessageRead({ conversation }), {
107
- wrapper: createWrapper({ replyRootId: 'root-1' }),
108
- })
109
-
110
- expect(markRead).not.toHaveBeenCalled()
111
- })
112
-
113
- it('does NOT fire markRead when JTU is active and user is not at end of history', () => {
114
- mockAppState()
115
- mockFeatures(true)
116
- const markRead = mockMarkRead()
117
-
118
- renderHook(() => useMarkLatestMessageRead({ conversation }), {
119
- wrapper: createWrapper({
120
- initialMessageIdIsAnchor: true,
121
- atEndOfMessageHistory: false,
122
- }),
123
- })
124
-
125
- expect(markRead).not.toHaveBeenCalled()
126
- })
127
-
128
- it('fires markRead when JTU is active and user reaches end of history', () => {
129
- mockAppState()
130
- mockFeatures(true)
131
- const markRead = mockMarkRead()
132
-
133
- renderHook(() => useMarkLatestMessageRead({ conversation }), {
134
- wrapper: createWrapper({
135
- initialMessageIdIsAnchor: true,
136
- atEndOfMessageHistory: true,
137
- }),
138
- })
139
-
140
- expect(markRead).toHaveBeenCalledWith(true)
141
- })
142
-
143
- it('does NOT fire when app state is not active', () => {
144
- mockAppState('background')
145
- mockFeatures(false)
146
- const markRead = mockMarkRead()
147
-
148
- renderHook(() => useMarkLatestMessageRead({ conversation }), {
149
- wrapper: createWrapper(),
150
- })
151
-
152
- expect(markRead).not.toHaveBeenCalled()
153
- })
154
- })
@@ -1,54 +0,0 @@
1
- import { buildTestQueryClient } from '../../../__utils__/query_client'
2
- import { hasUnloadedNewerPages } from '../../../utils/cache/messages_cache'
3
-
4
- const queryKey = ['messages-test']
5
-
6
- const buildClient = (data: unknown) => {
7
- const client = buildTestQueryClient()
8
- client.setQueryData(queryKey, data)
9
- return client
10
- }
11
-
12
- describe('hasUnloadedNewerPages', () => {
13
- it('returns false when the query has no cached data', () => {
14
- const client = buildTestQueryClient()
15
- expect(hasUnloadedNewerPages(client, queryKey)).toBe(false)
16
- })
17
-
18
- it('returns false when pages[0] has no next.idGt cursor', () => {
19
- const client = buildClient({
20
- pageParams: [{}],
21
- pages: [{ data: [], links: {}, meta: { count: 0, totalCount: 0 } }],
22
- })
23
- expect(hasUnloadedNewerPages(client, queryKey)).toBe(false)
24
- })
25
-
26
- it('returns false when next exists but idGt is absent', () => {
27
- const client = buildClient({
28
- pageParams: [{}],
29
- pages: [{ data: [], links: {}, meta: { count: 0, totalCount: 0, next: { idLt: '01A' } } }],
30
- })
31
- expect(hasUnloadedNewerPages(client, queryKey)).toBe(false)
32
- })
33
-
34
- it('returns true when pages[0].meta.next.idGt is set', () => {
35
- const client = buildClient({
36
- pageParams: [{}],
37
- pages: [
38
- { data: [], links: {}, meta: { count: 0, totalCount: 0, next: { idGt: '01KQSTAY' } } },
39
- ],
40
- })
41
- expect(hasUnloadedNewerPages(client, queryKey)).toBe(true)
42
- })
43
-
44
- it('inspects only the first page (the newest side after sort)', () => {
45
- const client = buildClient({
46
- pageParams: [{}, {}],
47
- pages: [
48
- { data: [], links: {}, meta: { count: 0, totalCount: 0 } },
49
- { data: [], links: {}, meta: { count: 0, totalCount: 0, next: { idGt: '01KQSTAY' } } },
50
- ],
51
- })
52
- expect(hasUnloadedNewerPages(client, queryKey)).toBe(false)
53
- })
54
- })