@planningcenter/chat-react-native 4.2.1-qa-staging.5 → 4.2.1-rc.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.
- package/build/components/conversation/message_form.d.ts +1 -2
- package/build/components/conversation/message_form.d.ts.map +1 -1
- package/build/components/conversation/message_form.js +51 -54
- package/build/components/conversation/message_form.js.map +1 -1
- package/build/components/conversation/message_list.d.ts +2 -3
- package/build/components/conversation/message_list.d.ts.map +1 -1
- package/build/components/conversation/message_list.js +66 -152
- package/build/components/conversation/message_list.js.map +1 -1
- package/build/contexts/api_provider.d.ts.map +1 -1
- package/build/contexts/api_provider.js +1 -2
- package/build/contexts/api_provider.js.map +1 -1
- package/build/contexts/conversation_context.d.ts +1 -3
- package/build/contexts/conversation_context.d.ts.map +1 -1
- package/build/contexts/conversation_context.js +18 -23
- package/build/contexts/conversation_context.js.map +1 -1
- package/build/hooks/index.d.ts +0 -1
- package/build/hooks/index.d.ts.map +1 -1
- package/build/hooks/index.js +0 -1
- package/build/hooks/index.js.map +1 -1
- package/build/hooks/paginator_meta.d.ts +1 -3
- package/build/hooks/paginator_meta.d.ts.map +1 -1
- package/build/hooks/paginator_meta.js +8 -25
- package/build/hooks/paginator_meta.js.map +1 -1
- package/build/hooks/use_api.d.ts.map +1 -1
- package/build/hooks/use_api.js +8 -5
- package/build/hooks/use_api.js.map +1 -1
- package/build/hooks/use_conversation_messages.d.ts +2 -9
- package/build/hooks/use_conversation_messages.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages.js +45 -84
- package/build/hooks/use_conversation_messages.js.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.d.ts +1 -17
- package/build/hooks/use_conversation_messages_jolt_events.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.js +80 -89
- package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
- package/build/hooks/use_mark_latest_message_read.d.ts +1 -2
- package/build/hooks/use_mark_latest_message_read.d.ts.map +1 -1
- package/build/hooks/use_mark_latest_message_read.js +3 -4
- package/build/hooks/use_mark_latest_message_read.js.map +1 -1
- package/build/hooks/use_message_create_or_update.d.ts.map +1 -1
- package/build/hooks/use_message_create_or_update.js +80 -91
- package/build/hooks/use_message_create_or_update.js.map +1 -1
- package/build/hooks/use_message_reaction_toggle.d.ts.map +1 -1
- package/build/hooks/use_message_reaction_toggle.js +40 -45
- package/build/hooks/use_message_reaction_toggle.js.map +1 -1
- package/build/hooks/use_suspense_api.d.ts.map +1 -1
- package/build/hooks/use_suspense_api.js +8 -5
- package/build/hooks/use_suspense_api.js.map +1 -1
- package/build/screens/conversation_new/components/services_form.js +8 -9
- package/build/screens/conversation_new/components/services_form.js.map +1 -1
- package/build/screens/conversation_screen.d.ts +0 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +254 -481
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/screens/message_actions_screen.d.ts.map +1 -1
- package/build/screens/message_actions_screen.js +26 -21
- package/build/screens/message_actions_screen.js.map +1 -1
- package/build/screens/message_report_screen.d.ts.map +1 -1
- package/build/screens/message_report_screen.js +44 -17
- package/build/screens/message_report_screen.js.map +1 -1
- package/build/screens/reactions_screen.d.ts.map +1 -1
- package/build/screens/reactions_screen.js +88 -70
- package/build/screens/reactions_screen.js.map +1 -1
- package/build/types/api_primitives.d.ts +0 -2
- package/build/types/api_primitives.d.ts.map +1 -1
- package/build/types/api_primitives.js.map +1 -1
- package/build/types/resources/member.d.ts +0 -1
- package/build/types/resources/member.d.ts.map +1 -1
- package/build/types/resources/member.js.map +1 -1
- package/build/utils/cache/messages_cache.d.ts +0 -1
- package/build/utils/cache/messages_cache.d.ts.map +1 -1
- package/build/utils/cache/messages_cache.js +0 -3
- package/build/utils/cache/messages_cache.js.map +1 -1
- package/build/utils/cache/optimistically_update_message.d.ts +1 -2
- package/build/utils/cache/optimistically_update_message.d.ts.map +1 -1
- package/build/utils/cache/optimistically_update_message.js +4 -4
- package/build/utils/cache/optimistically_update_message.js.map +1 -1
- package/build/utils/client/types.d.ts +0 -1
- package/build/utils/client/types.d.ts.map +1 -1
- package/build/utils/client/types.js.map +1 -1
- package/build/utils/generate_placeholder_ulid.d.ts +6 -0
- package/build/utils/generate_placeholder_ulid.d.ts.map +1 -1
- package/build/utils/generate_placeholder_ulid.js +2 -2
- package/build/utils/generate_placeholder_ulid.js.map +1 -1
- package/build/utils/request/get_messages.d.ts +6 -18
- package/build/utils/request/get_messages.d.ts.map +1 -1
- package/build/utils/request/get_messages.js +12 -10
- package/build/utils/request/get_messages.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/hooks/paginator_meta.test.ts +2 -73
- package/src/components/conversation/__tests__/message_list.test.tsx +0 -42
- package/src/components/conversation/message_form.tsx +0 -3
- package/src/components/conversation/message_list.tsx +4 -58
- package/src/contexts/api_provider.tsx +1 -2
- package/src/contexts/conversation_context.tsx +1 -6
- package/src/hooks/index.ts +0 -1
- package/src/hooks/paginator_meta.ts +5 -29
- package/src/hooks/use_api.ts +5 -4
- package/src/hooks/use_conversation_messages.ts +11 -52
- package/src/hooks/use_conversation_messages_jolt_events.ts +43 -62
- package/src/hooks/use_mark_latest_message_read.ts +3 -4
- package/src/hooks/use_message_create_or_update.ts +8 -8
- package/src/hooks/use_message_reaction_toggle.ts +1 -3
- package/src/hooks/use_suspense_api.ts +5 -4
- package/src/screens/conversation_new/components/services_form.tsx +5 -5
- package/src/screens/conversation_screen.tsx +14 -123
- package/src/screens/message_actions_screen.tsx +5 -3
- package/src/screens/message_report_screen.tsx +3 -2
- package/src/screens/reactions_screen.tsx +4 -2
- package/src/types/api_primitives.ts +0 -2
- package/src/types/resources/member.ts +0 -1
- package/src/utils/cache/__tests__/messages_cache.test.ts +1 -14
- package/src/utils/cache/messages_cache.ts +0 -3
- package/src/utils/cache/optimistically_update_message.ts +5 -2
- package/src/utils/client/types.ts +0 -1
- package/src/utils/generate_placeholder_ulid.ts +2 -2
- package/src/utils/request/get_messages.ts +14 -12
- package/build/components/conversation/anchor_not_found_boundary.d.ts +0 -18
- package/build/components/conversation/anchor_not_found_boundary.d.ts.map +0 -1
- package/build/components/conversation/anchor_not_found_boundary.js +0 -20
- package/build/components/conversation/anchor_not_found_boundary.js.map +0 -1
- package/build/hooks/use_conversation_message_lookup.d.ts +0 -7
- package/build/hooks/use_conversation_message_lookup.d.ts.map +0 -1
- package/build/hooks/use_conversation_message_lookup.js +0 -52
- package/build/hooks/use_conversation_message_lookup.js.map +0 -1
- package/build/hooks/use_direct_message_eligibility.d.ts +0 -14
- package/build/hooks/use_direct_message_eligibility.d.ts.map +0 -1
- package/build/hooks/use_direct_message_eligibility.js +0 -87
- package/build/hooks/use_direct_message_eligibility.js.map +0 -1
- package/build/hooks/use_fetch_newer_messages.d.ts +0 -9
- package/build/hooks/use_fetch_newer_messages.d.ts.map +0 -1
- package/build/hooks/use_fetch_newer_messages.js +0 -69
- package/build/hooks/use_fetch_newer_messages.js.map +0 -1
- package/build/hooks/use_fetch_newer_on_foreground.d.ts +0 -8
- package/build/hooks/use_fetch_newer_on_foreground.d.ts.map +0 -1
- package/build/hooks/use_fetch_newer_on_foreground.js +0 -42
- package/build/hooks/use_fetch_newer_on_foreground.js.map +0 -1
- package/build/hooks/use_held_back_messages.d.ts +0 -11
- package/build/hooks/use_held_back_messages.d.ts.map +0 -1
- package/build/hooks/use_held_back_messages.js +0 -24
- package/build/hooks/use_held_back_messages.js.map +0 -1
- package/build/utils/cache/find_message_in_caches.d.ts +0 -4
- package/build/utils/cache/find_message_in_caches.d.ts.map +0 -1
- package/build/utils/cache/find_message_in_caches.js +0 -16
- package/build/utils/cache/find_message_in_caches.js.map +0 -1
- package/build/utils/cache/invalidate_refetchable_queries.d.ts +0 -3
- package/build/utils/cache/invalidate_refetchable_queries.d.ts.map +0 -1
- package/build/utils/cache/invalidate_refetchable_queries.js +0 -8
- package/build/utils/cache/invalidate_refetchable_queries.js.map +0 -1
- package/build/utils/cache/prepend_newer_page.d.ts +0 -7
- package/build/utils/cache/prepend_newer_page.d.ts.map +0 -1
- package/build/utils/cache/prepend_newer_page.js +0 -15
- package/build/utils/cache/prepend_newer_page.js.map +0 -1
- package/build/utils/gated_message_cache.d.ts +0 -7
- package/build/utils/gated_message_cache.d.ts.map +0 -1
- package/build/utils/gated_message_cache.js +0 -13
- package/build/utils/gated_message_cache.js.map +0 -1
- package/build/utils/ulid.d.ts +0 -3
- package/build/utils/ulid.d.ts.map +0 -1
- package/build/utils/ulid.js +0 -7
- package/build/utils/ulid.js.map +0 -1
- package/src/__tests__/hooks/use_conversation_messages.test.ts +0 -22
- package/src/__tests__/hooks/use_conversation_messages_jolt_events.test.ts +0 -33
- package/src/__tests__/hooks/use_direct_message_eligibility.test.tsx +0 -83
- package/src/__tests__/screens/conversation_new/components/services_form.test.tsx +0 -91
- package/src/__tests__/utils/gated_message_cache.test.ts +0 -26
- package/src/__tests__/utils/request/get_messages.test.ts +0 -24
- package/src/__tests__/utils/ulid.test.ts +0 -15
- package/src/components/conversation/__tests__/anchor_not_found_boundary.test.tsx +0 -51
- package/src/components/conversation/anchor_not_found_boundary.tsx +0 -21
- package/src/hooks/__tests__/use_fetch_newer_messages.test.tsx +0 -95
- package/src/hooks/__tests__/use_held_back_messages.test.tsx +0 -60
- package/src/hooks/__tests__/use_message_reaction_toggle.test.tsx +0 -73
- package/src/hooks/use_conversation_message_lookup.ts +0 -29
- package/src/hooks/use_direct_message_eligibility.ts +0 -40
- package/src/hooks/use_fetch_newer_messages.ts +0 -52
- package/src/hooks/use_fetch_newer_on_foreground.ts +0 -25
- package/src/hooks/use_held_back_messages.ts +0 -33
- package/src/utils/cache/__tests__/find_message_in_caches.test.ts +0 -37
- package/src/utils/cache/__tests__/invalidate_refetchable_queries.test.ts +0 -23
- package/src/utils/cache/__tests__/prepend_newer_page.test.ts +0 -34
- package/src/utils/cache/find_message_in_caches.ts +0 -20
- package/src/utils/cache/invalidate_refetchable_queries.ts +0 -9
- package/src/utils/cache/prepend_newer_page.ts +0 -19
- package/src/utils/gated_message_cache.ts +0 -19
- package/src/utils/ulid.ts +0 -11
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { InfiniteData, QueryClientProvider } from '@tanstack/react-query'
|
|
2
|
-
import { act, renderHook } from '@testing-library/react-native'
|
|
3
|
-
import React from 'react'
|
|
4
|
-
import { buildTestQueryClient } from '../../__utils__/query_client'
|
|
5
|
-
import type { ApiCollection, MessageResource } from '../../types'
|
|
6
|
-
import { getMessagesQueryKey } from '../../utils/request/get_messages'
|
|
7
|
-
import { ApiClient, useApiClient } from '../use_api_client'
|
|
8
|
-
import { useFetchNewerMessages } from '../use_fetch_newer_messages'
|
|
9
|
-
|
|
10
|
-
jest.mock('../use_api_client')
|
|
11
|
-
|
|
12
|
-
type QueryData = InfiniteData<ApiCollection<MessageResource>>
|
|
13
|
-
|
|
14
|
-
const page = (ids: string[], next?: { idGt: string }) => ({
|
|
15
|
-
data: ids.map(id => ({ id, type: 'Message' }) as unknown as MessageResource),
|
|
16
|
-
links: {},
|
|
17
|
-
meta: { count: ids.length, totalCount: ids.length, next },
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
const queryKey = getMessagesQueryKey({ conversation_id: 1 })
|
|
21
|
-
let queryClient = buildTestQueryClient()
|
|
22
|
-
const get = jest.fn()
|
|
23
|
-
|
|
24
|
-
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
25
|
-
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
const cachedIds = () =>
|
|
29
|
-
queryClient.getQueryData<QueryData>(queryKey)?.pages.flatMap(p => p.data.map(m => m.id))
|
|
30
|
-
|
|
31
|
-
beforeEach(() => {
|
|
32
|
-
queryClient = buildTestQueryClient()
|
|
33
|
-
queryClient.setQueryData<QueryData>(queryKey, {
|
|
34
|
-
pages: [page(['01C', '01B']), page(['01A'])],
|
|
35
|
-
pageParams: [{}, { where: { id_lt: '01B' } }],
|
|
36
|
-
})
|
|
37
|
-
get.mockReset()
|
|
38
|
-
;(useApiClient as jest.MockedFunction<typeof useApiClient>).mockReturnValue({
|
|
39
|
-
chat: { get },
|
|
40
|
-
} as unknown as ApiClient)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
describe('useFetchNewerMessages', () => {
|
|
44
|
-
it('follows meta.next until everything newer is in the cache', async () => {
|
|
45
|
-
get
|
|
46
|
-
.mockResolvedValueOnce(page(['01D', '01E'], { idGt: '01E' }))
|
|
47
|
-
.mockResolvedValueOnce(page(['01F']))
|
|
48
|
-
const { result } = renderHook(() => useFetchNewerMessages({ conversation_id: 1, queryKey }), {
|
|
49
|
-
wrapper,
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
await act(() => result.current('01C'))
|
|
53
|
-
|
|
54
|
-
expect(get).toHaveBeenCalledTimes(2)
|
|
55
|
-
expect(get.mock.calls[1][0].data.where).toEqual({ id_gt: '01E' })
|
|
56
|
-
expect(cachedIds()).toEqual(['01D', '01E', '01F', '01C', '01B', '01A'])
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('rejects when the request fails so the caller can try again later', async () => {
|
|
60
|
-
get.mockRejectedValueOnce(new Error('offline'))
|
|
61
|
-
const { result } = renderHook(() => useFetchNewerMessages({ conversation_id: 1, queryKey }), {
|
|
62
|
-
wrapper,
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
await expect(act(() => result.current('01C'))).rejects.toThrow('offline')
|
|
66
|
-
expect(cachedIds()).toEqual(['01C', '01B', '01A'])
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it('hands off instead of refetching when more is left than it will walk', async () => {
|
|
70
|
-
for (let i = 0; i < 5; i++) get.mockResolvedValueOnce(page([`01D${i}`], { idGt: `01D${i}` }))
|
|
71
|
-
const onTooFarBehind = jest.fn()
|
|
72
|
-
const invalidate = jest.spyOn(queryClient, 'invalidateQueries')
|
|
73
|
-
const { result } = renderHook(
|
|
74
|
-
() => useFetchNewerMessages({ conversation_id: 1, queryKey, onTooFarBehind }),
|
|
75
|
-
{ wrapper }
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
await act(() => result.current('01C'))
|
|
79
|
-
|
|
80
|
-
expect(onTooFarBehind).toHaveBeenCalledTimes(1)
|
|
81
|
-
expect(invalidate).not.toHaveBeenCalled()
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('refetches the live cache when more is left and nobody takes over', async () => {
|
|
85
|
-
for (let i = 0; i < 5; i++) get.mockResolvedValueOnce(page([`01D${i}`], { idGt: `01D${i}` }))
|
|
86
|
-
const invalidate = jest.spyOn(queryClient, 'invalidateQueries')
|
|
87
|
-
const { result } = renderHook(() => useFetchNewerMessages({ conversation_id: 1, queryKey }), {
|
|
88
|
-
wrapper,
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
await act(() => result.current('01C'))
|
|
92
|
-
|
|
93
|
-
expect(invalidate).toHaveBeenCalledWith({ queryKey })
|
|
94
|
-
})
|
|
95
|
-
})
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { act, renderHook } from '@testing-library/react-native'
|
|
2
|
-
import { useHeldBackMessages } from '../use_held_back_messages'
|
|
3
|
-
|
|
4
|
-
type Props = { newestMessageId?: string; readingHistory: boolean; caughtUp: boolean }
|
|
5
|
-
|
|
6
|
-
const setup = (fetchNewer: jest.Mock, initial: Props) =>
|
|
7
|
-
renderHook((props: Props) => useHeldBackMessages({ fetchNewer, ...props }), {
|
|
8
|
-
initialProps: initial,
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
describe('useHeldBackMessages', () => {
|
|
12
|
-
it('catches up from where the first skip happened, not from the newest message', async () => {
|
|
13
|
-
const fetchNewer = jest.fn().mockResolvedValue(undefined)
|
|
14
|
-
const { result, rerender } = setup(fetchNewer, {
|
|
15
|
-
newestMessageId: 'A',
|
|
16
|
-
readingHistory: true,
|
|
17
|
-
caughtUp: true,
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
act(() => result.current.onSkippedNewMessage())
|
|
21
|
-
rerender({ newestMessageId: 'B', readingHistory: true, caughtUp: true })
|
|
22
|
-
await act(async () => rerender({ newestMessageId: 'B', readingHistory: false, caughtUp: true }))
|
|
23
|
-
|
|
24
|
-
expect(fetchNewer).toHaveBeenCalledTimes(1)
|
|
25
|
-
expect(fetchNewer).toHaveBeenCalledWith('A')
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it('keeps the cursor when the catch-up fails and tries again next time', async () => {
|
|
29
|
-
const fetchNewer = jest
|
|
30
|
-
.fn()
|
|
31
|
-
.mockRejectedValueOnce(new Error('offline'))
|
|
32
|
-
.mockResolvedValue(undefined)
|
|
33
|
-
const { result, rerender } = setup(fetchNewer, {
|
|
34
|
-
newestMessageId: 'A',
|
|
35
|
-
readingHistory: true,
|
|
36
|
-
caughtUp: true,
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
act(() => result.current.onSkippedNewMessage())
|
|
40
|
-
await act(async () => rerender({ newestMessageId: 'A', readingHistory: false, caughtUp: true }))
|
|
41
|
-
rerender({ newestMessageId: 'C', readingHistory: true, caughtUp: true })
|
|
42
|
-
await act(async () => rerender({ newestMessageId: 'C', readingHistory: false, caughtUp: true }))
|
|
43
|
-
|
|
44
|
-
expect(fetchNewer).toHaveBeenCalledTimes(2)
|
|
45
|
-
expect(fetchNewer).toHaveBeenLastCalledWith('A')
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it('does nothing when nothing was held back', async () => {
|
|
49
|
-
const fetchNewer = jest.fn()
|
|
50
|
-
const { rerender } = setup(fetchNewer, {
|
|
51
|
-
newestMessageId: 'A',
|
|
52
|
-
readingHistory: true,
|
|
53
|
-
caughtUp: true,
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
await act(async () => rerender({ newestMessageId: 'A', readingHistory: false, caughtUp: true }))
|
|
57
|
-
|
|
58
|
-
expect(fetchNewer).not.toHaveBeenCalled()
|
|
59
|
-
})
|
|
60
|
-
})
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { InfiniteData, QueryClientProvider } from '@tanstack/react-query'
|
|
2
|
-
import { act, renderHook } from '@testing-library/react-native'
|
|
3
|
-
import React from 'react'
|
|
4
|
-
import { buildTestQueryClient } from '../../__utils__/query_client'
|
|
5
|
-
import { ConversationContextProvider } from '../../contexts/conversation_context'
|
|
6
|
-
import type { ApiCollection, MessageResource } from '../../types'
|
|
7
|
-
import { getMessagesQueryKey } from '../../utils/request/get_messages'
|
|
8
|
-
import { ApiClient, useApiClient } from '../use_api_client'
|
|
9
|
-
import { useMessageReactionToggle } from '../use_message_reaction_toggle'
|
|
10
|
-
|
|
11
|
-
jest.mock('../use_api_client')
|
|
12
|
-
|
|
13
|
-
type QueryData = InfiniteData<ApiCollection<MessageResource>>
|
|
14
|
-
|
|
15
|
-
const message = { id: '01M', type: 'Message', reactionCounts: [] } as unknown as MessageResource
|
|
16
|
-
const liveKey = getMessagesQueryKey({ conversation_id: 1 })
|
|
17
|
-
const anchoredKey = getMessagesQueryKey({ conversation_id: 1, anchor: '01A' })
|
|
18
|
-
const seed = (): QueryData => ({
|
|
19
|
-
pages: [{ data: [message], links: {}, meta: { count: 1, totalCount: 1 } }],
|
|
20
|
-
pageParams: [{}],
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
const heartsIn = (queryClient: ReturnType<typeof buildTestQueryClient>, key: unknown[]) =>
|
|
24
|
-
queryClient
|
|
25
|
-
.getQueryData<QueryData>(key)
|
|
26
|
-
?.pages[0].data[0].reactionCounts.map(r => `${r.value}:${r.count}`)
|
|
27
|
-
|
|
28
|
-
describe('useMessageReactionToggle', () => {
|
|
29
|
-
beforeEach(() => {
|
|
30
|
-
;(useApiClient as jest.MockedFunction<typeof useApiClient>).mockReturnValue({
|
|
31
|
-
chat: { post: jest.fn(() => new Promise(() => {})) },
|
|
32
|
-
} as unknown as ApiClient)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('applies the optimistic reaction to the cache the screen is showing', async () => {
|
|
36
|
-
const queryClient = buildTestQueryClient({ defaultOptions: { queries: { gcTime: Infinity } } })
|
|
37
|
-
queryClient.setQueryData<QueryData>(anchoredKey, seed())
|
|
38
|
-
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
39
|
-
<QueryClientProvider client={queryClient}>
|
|
40
|
-
<ConversationContextProvider
|
|
41
|
-
conversationId={1}
|
|
42
|
-
currentPageReplyRootId={null}
|
|
43
|
-
messagesQueryKey={anchoredKey}
|
|
44
|
-
>
|
|
45
|
-
{children}
|
|
46
|
-
</ConversationContextProvider>
|
|
47
|
-
</QueryClientProvider>
|
|
48
|
-
)
|
|
49
|
-
const { result } = renderHook(() => useMessageReactionToggle({ conversation_id: 1, message }), {
|
|
50
|
-
wrapper,
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
await act(async () => result.current.handleReactionToggle({ value: 'heart', mine: false }))
|
|
54
|
-
|
|
55
|
-
expect(heartsIn(queryClient, anchoredKey)).toEqual(['heart:1'])
|
|
56
|
-
expect(queryClient.getQueryData(liveKey)).toBeUndefined()
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('falls back to the live cache when no screen key is provided', async () => {
|
|
60
|
-
const queryClient = buildTestQueryClient({ defaultOptions: { queries: { gcTime: Infinity } } })
|
|
61
|
-
queryClient.setQueryData<QueryData>(liveKey, seed())
|
|
62
|
-
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
63
|
-
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
64
|
-
)
|
|
65
|
-
const { result } = renderHook(() => useMessageReactionToggle({ conversation_id: 1, message }), {
|
|
66
|
-
wrapper,
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
await act(async () => result.current.handleReactionToggle({ value: 'heart', mine: false }))
|
|
70
|
-
|
|
71
|
-
expect(heartsIn(queryClient, liveKey)).toEqual(['heart:1'])
|
|
72
|
-
})
|
|
73
|
-
})
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { useQueryClient } from '@tanstack/react-query'
|
|
2
|
-
import { useCallback, useSyncExternalStore } from 'react'
|
|
3
|
-
import { findMessageInCaches } from '../utils/cache/find_message_in_caches'
|
|
4
|
-
import { useConversationMessage } from './use_conversation_message'
|
|
5
|
-
|
|
6
|
-
type Options = {
|
|
7
|
-
conversation_id: number
|
|
8
|
-
message_id: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const useConversationMessageLookup = ({ conversation_id, message_id }: Options) => {
|
|
12
|
-
const queryClient = useQueryClient()
|
|
13
|
-
const subscribe = useCallback(
|
|
14
|
-
(onChange: () => void) => queryClient.getQueryCache().subscribe(onChange),
|
|
15
|
-
[queryClient]
|
|
16
|
-
)
|
|
17
|
-
const getSnapshot = useCallback(
|
|
18
|
-
() => findMessageInCaches(queryClient, conversation_id, message_id),
|
|
19
|
-
[conversation_id, message_id, queryClient]
|
|
20
|
-
)
|
|
21
|
-
const cached = useSyncExternalStore(subscribe, getSnapshot)
|
|
22
|
-
const fetched = useConversationMessage({
|
|
23
|
-
conversation_id,
|
|
24
|
-
messageId: message_id,
|
|
25
|
-
enabled: !cached,
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
return cached ?? fetched.message
|
|
29
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { useQuery } from '@tanstack/react-query'
|
|
2
|
-
import { ApiResource, MemberResource } from '../types'
|
|
3
|
-
import { useApiClient } from './use_api_client'
|
|
4
|
-
|
|
5
|
-
export interface DirectMessageEligibility {
|
|
6
|
-
canDirectMessage: boolean
|
|
7
|
-
directMessagesDisabled: boolean
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function useDirectMessageEligibility({
|
|
11
|
-
groupId,
|
|
12
|
-
personId,
|
|
13
|
-
enabled = true,
|
|
14
|
-
}: {
|
|
15
|
-
groupId: string
|
|
16
|
-
personId: number
|
|
17
|
-
enabled?: boolean
|
|
18
|
-
}) {
|
|
19
|
-
const apiClient = useApiClient()
|
|
20
|
-
const url = `/me/groups/${groupId}/members/${personId}`
|
|
21
|
-
|
|
22
|
-
const { data, isLoading, isError } = useQuery({
|
|
23
|
-
queryKey: ['chat', url, 'direct_message_eligibility'],
|
|
24
|
-
queryFn: () =>
|
|
25
|
-
apiClient.chat.get({
|
|
26
|
-
url,
|
|
27
|
-
data: { fields: { Member: ['can_direct_message', 'direct_messages_disabled'] } },
|
|
28
|
-
}) as Promise<ApiResource<MemberResource>>,
|
|
29
|
-
enabled,
|
|
30
|
-
retry: false,
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
const member = data?.data
|
|
34
|
-
const eligibility: DirectMessageEligibility | undefined = member && {
|
|
35
|
-
canDirectMessage: member.canDirectMessage ?? false,
|
|
36
|
-
directMessagesDisabled: member.directMessagesDisabled ?? false,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return { eligibility, isLoading, isError }
|
|
40
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { useQueryClient } from '@tanstack/react-query'
|
|
2
|
-
import { useCallback } from 'react'
|
|
3
|
-
import { ApiCollection, MessageResource } from '../types'
|
|
4
|
-
import { prependNewerPage } from '../utils/cache/prepend_newer_page'
|
|
5
|
-
import { getMessagesRequestArgs } from '../utils/request/get_messages'
|
|
6
|
-
import { useApiClient } from './use_api_client'
|
|
7
|
-
|
|
8
|
-
const MAX_PAGES = 5
|
|
9
|
-
|
|
10
|
-
type Options = {
|
|
11
|
-
conversation_id: number
|
|
12
|
-
reply_root_id?: string | null
|
|
13
|
-
queryKey: unknown[]
|
|
14
|
-
onTooFarBehind?: () => void
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const useFetchNewerMessages = ({
|
|
18
|
-
conversation_id,
|
|
19
|
-
reply_root_id,
|
|
20
|
-
queryKey,
|
|
21
|
-
onTooFarBehind,
|
|
22
|
-
}: Options) => {
|
|
23
|
-
const queryClient = useQueryClient()
|
|
24
|
-
const apiClient = useApiClient()
|
|
25
|
-
|
|
26
|
-
return useCallback(
|
|
27
|
-
async (newestMessageId: string) => {
|
|
28
|
-
const { url, data } = getMessagesRequestArgs({ conversation_id, reply_root_id })
|
|
29
|
-
const pages: ApiCollection<MessageResource>[] = []
|
|
30
|
-
let cursor: string | undefined = newestMessageId
|
|
31
|
-
|
|
32
|
-
while (cursor && pages.length < MAX_PAGES) {
|
|
33
|
-
const page: ApiCollection<MessageResource> = await apiClient.chat.get({
|
|
34
|
-
url,
|
|
35
|
-
data: { ...data, where: { id_gt: cursor }, order: 'asc' },
|
|
36
|
-
})
|
|
37
|
-
pages.push(page)
|
|
38
|
-
cursor = page.meta?.next?.idGt
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
queryClient.setQueryData<Parameters<typeof prependNewerPage<MessageResource>>[0]>(
|
|
42
|
-
queryKey,
|
|
43
|
-
prev => prependNewerPage(prev, pages)
|
|
44
|
-
)
|
|
45
|
-
const moreThanWeFetched = !!cursor
|
|
46
|
-
if (!moreThanWeFetched) return
|
|
47
|
-
if (onTooFarBehind) onTooFarBehind()
|
|
48
|
-
else await queryClient.invalidateQueries({ queryKey })
|
|
49
|
-
},
|
|
50
|
-
[apiClient, conversation_id, onTooFarBehind, queryClient, queryKey, reply_root_id]
|
|
51
|
-
)
|
|
52
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react'
|
|
2
|
-
import { useAppState } from './use_app_state'
|
|
3
|
-
|
|
4
|
-
type Options = {
|
|
5
|
-
fetchNewer: (newestMessageId: string) => Promise<void>
|
|
6
|
-
newestMessageId?: string
|
|
7
|
-
enabled: boolean
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const useFetchNewerOnForeground = ({ fetchNewer, newestMessageId, enabled }: Options) => {
|
|
11
|
-
const appState = useAppState()
|
|
12
|
-
const wasInBackground = useRef(false)
|
|
13
|
-
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
if (appState !== 'active') {
|
|
16
|
-
wasInBackground.current = true
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
const returnedToForeground = wasInBackground.current
|
|
20
|
-
wasInBackground.current = false
|
|
21
|
-
if (!returnedToForeground || !enabled || !newestMessageId) return
|
|
22
|
-
|
|
23
|
-
fetchNewer(newestMessageId).catch(() => {})
|
|
24
|
-
}, [appState, enabled, fetchNewer, newestMessageId])
|
|
25
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef } from 'react'
|
|
2
|
-
|
|
3
|
-
type Options = {
|
|
4
|
-
fetchNewer: (newestMessageId: string) => Promise<void>
|
|
5
|
-
newestMessageId?: string
|
|
6
|
-
readingHistory: boolean
|
|
7
|
-
caughtUp: boolean
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const useHeldBackMessages = ({
|
|
11
|
-
fetchNewer,
|
|
12
|
-
newestMessageId,
|
|
13
|
-
readingHistory,
|
|
14
|
-
caughtUp,
|
|
15
|
-
}: Options) => {
|
|
16
|
-
const heldBackSince = useRef<string | null>(null)
|
|
17
|
-
|
|
18
|
-
const onSkippedNewMessage = useCallback(() => {
|
|
19
|
-
heldBackSince.current ??= newestMessageId ?? null
|
|
20
|
-
}, [newestMessageId])
|
|
21
|
-
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
const since = heldBackSince.current
|
|
24
|
-
if (readingHistory || !caughtUp || !since) return
|
|
25
|
-
|
|
26
|
-
heldBackSince.current = null
|
|
27
|
-
fetchNewer(since).catch(() => {
|
|
28
|
-
heldBackSince.current ??= since
|
|
29
|
-
})
|
|
30
|
-
}, [caughtUp, fetchNewer, readingHistory])
|
|
31
|
-
|
|
32
|
-
return { onSkippedNewMessage }
|
|
33
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { QueryClient } from '@tanstack/react-query'
|
|
2
|
-
import { getMessagesQueryKey } from '../../request/get_messages'
|
|
3
|
-
import { findMessageInCaches } from '../find_message_in_caches'
|
|
4
|
-
|
|
5
|
-
const page = (ids: string[]) => ({
|
|
6
|
-
data: ids.map(id => ({ id, type: 'Message' })),
|
|
7
|
-
links: {},
|
|
8
|
-
meta: { count: ids.length, totalCount: ids.length },
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
const seed = (queryClient: QueryClient, key: unknown[], ids: string[]) =>
|
|
12
|
-
queryClient.setQueryData(key, { pages: [page(ids)], pageParams: [{}] })
|
|
13
|
-
|
|
14
|
-
describe('findMessageInCaches', () => {
|
|
15
|
-
const queryClient = new QueryClient()
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
queryClient.clear()
|
|
19
|
-
seed(queryClient, getMessagesQueryKey({ conversation_id: 1 }), ['01C', '01B'])
|
|
20
|
-
seed(queryClient, getMessagesQueryKey({ conversation_id: 1, anchor: '01A' }), ['01A', '00Z'])
|
|
21
|
-
seed(queryClient, getMessagesQueryKey({ conversation_id: 1, reply_root_id: '01B' }), ['01R'])
|
|
22
|
-
seed(queryClient, getMessagesQueryKey({ conversation_id: 2 }), ['02A'])
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('finds a message in the live cache', () => {
|
|
26
|
-
expect(findMessageInCaches(queryClient, 1, '01B')?.id).toBe('01B')
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('finds a message that only the anchored cache holds', () => {
|
|
30
|
-
expect(findMessageInCaches(queryClient, 1, '00Z')?.id).toBe('00Z')
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('ignores reply threads and other conversations', () => {
|
|
34
|
-
expect(findMessageInCaches(queryClient, 1, '01R')).toBeUndefined()
|
|
35
|
-
expect(findMessageInCaches(queryClient, 1, '02A')).toBeUndefined()
|
|
36
|
-
})
|
|
37
|
-
})
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { QueryClient } from '@tanstack/react-query'
|
|
2
|
-
import { getMessagesQueryKey } from '../../request/get_messages'
|
|
3
|
-
import { invalidateRefetchableQueries } from '../invalidate_refetchable_queries'
|
|
4
|
-
|
|
5
|
-
describe('invalidateRefetchableQueries', () => {
|
|
6
|
-
const queryClient = new QueryClient()
|
|
7
|
-
const live = getMessagesQueryKey({ conversation_id: 1 })
|
|
8
|
-
const anchored = getMessagesQueryKey({ conversation_id: 1, anchor: '01A' })
|
|
9
|
-
const other = ['/me/conversations']
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
queryClient.clear()
|
|
13
|
-
for (const key of [live, anchored, other]) queryClient.setQueryData(key, { pages: [], pageParams: [] })
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it('invalidates everything except anchored message windows', async () => {
|
|
17
|
-
await invalidateRefetchableQueries(queryClient)
|
|
18
|
-
|
|
19
|
-
expect(queryClient.getQueryState(live)?.isInvalidated).toBe(true)
|
|
20
|
-
expect(queryClient.getQueryState(other)?.isInvalidated).toBe(true)
|
|
21
|
-
expect(queryClient.getQueryState(anchored)?.isInvalidated).toBe(false)
|
|
22
|
-
})
|
|
23
|
-
})
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { prependNewerPage } from '../prepend_newer_page'
|
|
2
|
-
|
|
3
|
-
const page = (ids: string[], next?: { idGt: string }) => ({
|
|
4
|
-
data: ids.map(id => ({ id, type: 'Message' })),
|
|
5
|
-
links: {},
|
|
6
|
-
meta: { count: ids.length, totalCount: ids.length, next },
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
describe('prependNewerPage', () => {
|
|
10
|
-
const cached = { pages: [page(['01C', '01B']), page(['01A'])], pageParams: [{}, { offset: 1 }] }
|
|
11
|
-
|
|
12
|
-
it('merges the new rows into the first page and leaves the page params alone', () => {
|
|
13
|
-
const result = prependNewerPage(cached, [page(['01C', '01D', '01E'])])
|
|
14
|
-
|
|
15
|
-
expect(result?.pages).toHaveLength(2)
|
|
16
|
-
expect(result?.pages[0].data.map(m => m.id)).toEqual(['01D', '01E', '01C', '01B'])
|
|
17
|
-
expect(result?.pageParams).toEqual(cached.pageParams)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('merges every fetched batch, newest last', () => {
|
|
21
|
-
const result = prependNewerPage(cached, [page(['01D', '01E']), page(['01F'])])
|
|
22
|
-
|
|
23
|
-
expect(result?.pages[0].data.map(m => m.id)).toEqual(['01D', '01E', '01F', '01C', '01B'])
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('returns the same data when nothing new came back', () => {
|
|
27
|
-
expect(prependNewerPage(cached, [page(['01C'])])).toBe(cached)
|
|
28
|
-
expect(prependNewerPage(cached, [])).toBe(cached)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('does nothing without cached data', () => {
|
|
32
|
-
expect(prependNewerPage(undefined, [page(['01D'])])).toBeUndefined()
|
|
33
|
-
})
|
|
34
|
-
})
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { InfiniteData, QueryClient } from '@tanstack/react-query'
|
|
2
|
-
import { ApiCollection, MessageResource } from '../../types'
|
|
3
|
-
import { getMessagesRequestArgs } from '../request/get_messages'
|
|
4
|
-
|
|
5
|
-
type QueryData = InfiniteData<ApiCollection<MessageResource>>
|
|
6
|
-
|
|
7
|
-
export const findMessageInCaches = (
|
|
8
|
-
queryClient: QueryClient,
|
|
9
|
-
conversation_id: number,
|
|
10
|
-
message_id: string
|
|
11
|
-
): MessageResource | undefined => {
|
|
12
|
-
const { url } = getMessagesRequestArgs({ conversation_id })
|
|
13
|
-
|
|
14
|
-
for (const [, data] of queryClient.getQueriesData<QueryData>({ queryKey: [url] })) {
|
|
15
|
-
const found = data?.pages.flatMap(page => page.data).find(message => message.id === message_id)
|
|
16
|
-
if (found) return found
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return undefined
|
|
20
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { Query, QueryClient } from '@tanstack/react-query'
|
|
2
|
-
|
|
3
|
-
const isAnchoredMessagesWindow = (query: Query) => {
|
|
4
|
-
const [, params] = query.queryKey as [unknown, { where?: { id_around?: string } } | undefined]
|
|
5
|
-
return !!params?.where?.id_around
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const invalidateRefetchableQueries = (queryClient: QueryClient) =>
|
|
9
|
-
queryClient.invalidateQueries({ predicate: query => !isAnchoredMessagesWindow(query) })
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { InfiniteData } from '@tanstack/react-query'
|
|
2
|
-
import { ApiCollection, ResourceObject } from '../../types'
|
|
3
|
-
import { RequestData } from '../client'
|
|
4
|
-
|
|
5
|
-
type PagesData<T extends ResourceObject> = InfiniteData<ApiCollection<T>, Partial<RequestData>>
|
|
6
|
-
|
|
7
|
-
export function prependNewerPage<T extends ResourceObject>(
|
|
8
|
-
data: PagesData<T> | undefined,
|
|
9
|
-
pages: ApiCollection<T>[]
|
|
10
|
-
): PagesData<T> | undefined {
|
|
11
|
-
if (!data || data.pages.length === 0) return data
|
|
12
|
-
|
|
13
|
-
const knownIds = new Set(data.pages.flatMap(page => page.data.map(record => record.id)))
|
|
14
|
-
const fresh = pages.flatMap(page => page.data).filter(record => !knownIds.has(record.id))
|
|
15
|
-
if (fresh.length === 0) return data
|
|
16
|
-
|
|
17
|
-
const [first, ...rest] = data.pages
|
|
18
|
-
return { ...data, pages: [{ ...first, data: [...fresh, ...first.data] }, ...rest] }
|
|
19
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { MessagesCache } from '../hooks/use_conversation_messages_jolt_events'
|
|
2
|
-
|
|
3
|
-
export const gatedMessageCache = ({
|
|
4
|
-
queryKey,
|
|
5
|
-
acceptNewMessages,
|
|
6
|
-
onSkippedNewMessage,
|
|
7
|
-
}: {
|
|
8
|
-
queryKey: unknown[]
|
|
9
|
-
acceptNewMessages: boolean
|
|
10
|
-
onSkippedNewMessage?: () => void
|
|
11
|
-
}): MessagesCache => ({
|
|
12
|
-
queryKey,
|
|
13
|
-
canUpdate: event => {
|
|
14
|
-
if (event !== 'message.created' || acceptNewMessages) return true
|
|
15
|
-
|
|
16
|
-
onSkippedNewMessage?.()
|
|
17
|
-
return false
|
|
18
|
-
},
|
|
19
|
-
})
|
package/src/utils/ulid.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export const ULID_CHARS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ' // Crockford's Base32
|
|
2
|
-
|
|
3
|
-
const TIME_LENGTH = 10
|
|
4
|
-
|
|
5
|
-
export function decodeUlidTime(ulid: string): number {
|
|
6
|
-
return ulid
|
|
7
|
-
.slice(0, TIME_LENGTH)
|
|
8
|
-
.toUpperCase()
|
|
9
|
-
.split('')
|
|
10
|
-
.reduce((time, char) => time * 32 + ULID_CHARS.indexOf(char), 0)
|
|
11
|
-
}
|