@linktr.ee/messaging-react 1.28.0 → 1.29.0-rc-1776320021
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/package.json
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from 'stream-chat-react'
|
|
18
18
|
|
|
19
19
|
import { useChannelStar } from '../hooks/useChannelStar'
|
|
20
|
+
import { useRestorePendingMessages } from '../hooks/useRestorePendingMessages'
|
|
20
21
|
import type { ChannelViewProps, LockedAttachmentSource } from '../types'
|
|
21
22
|
|
|
22
23
|
import { Avatar } from './Avatar'
|
|
@@ -241,6 +242,7 @@ const ChannelViewInner: React.FC<{
|
|
|
241
242
|
renderMessage,
|
|
242
243
|
}) => {
|
|
243
244
|
const { channel } = useChannelStateContext()
|
|
245
|
+
useRestorePendingMessages(channel)
|
|
244
246
|
const infoDialogRef = useRef<HTMLDialogElement>(null)
|
|
245
247
|
|
|
246
248
|
// Get participant info for info dialog
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react'
|
|
2
|
+
import type { Channel } from 'stream-chat'
|
|
3
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
4
|
+
|
|
5
|
+
import { useRestorePendingMessages } from './useRestorePendingMessages'
|
|
6
|
+
|
|
7
|
+
const createChannel = (
|
|
8
|
+
overrides: {
|
|
9
|
+
cid?: string
|
|
10
|
+
pending_messages?: Array<{
|
|
11
|
+
message: Record<string, unknown>
|
|
12
|
+
pending_message_metadata?: Record<string, string>
|
|
13
|
+
}>
|
|
14
|
+
} = {}
|
|
15
|
+
) =>
|
|
16
|
+
({
|
|
17
|
+
cid: overrides.cid ?? 'messaging:channel-1',
|
|
18
|
+
state: {
|
|
19
|
+
pending_messages: overrides.pending_messages ?? [],
|
|
20
|
+
addMessageSorted: vi.fn(),
|
|
21
|
+
},
|
|
22
|
+
}) as unknown as Channel
|
|
23
|
+
|
|
24
|
+
describe('useRestorePendingMessages', () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
vi.clearAllMocks()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('adds all pending messages to channel state', () => {
|
|
30
|
+
const pendingMsg = {
|
|
31
|
+
message: {
|
|
32
|
+
id: 'msg-1',
|
|
33
|
+
text: 'Hello',
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
const channel = createChannel({ pending_messages: [pendingMsg] })
|
|
37
|
+
|
|
38
|
+
renderHook(() => useRestorePendingMessages(channel))
|
|
39
|
+
|
|
40
|
+
expect(channel.state.addMessageSorted).toHaveBeenCalledOnce()
|
|
41
|
+
expect(channel.state.addMessageSorted).toHaveBeenCalledWith(
|
|
42
|
+
pendingMsg.message
|
|
43
|
+
)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('restores multiple pending messages', () => {
|
|
47
|
+
const msg1 = {
|
|
48
|
+
message: {
|
|
49
|
+
id: 'msg-1',
|
|
50
|
+
text: 'First message',
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
const msg2 = {
|
|
54
|
+
message: {
|
|
55
|
+
id: 'msg-2',
|
|
56
|
+
text: 'Second message',
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
const channel = createChannel({ pending_messages: [msg1, msg2] })
|
|
60
|
+
|
|
61
|
+
renderHook(() => useRestorePendingMessages(channel))
|
|
62
|
+
|
|
63
|
+
expect(channel.state.addMessageSorted).toHaveBeenCalledTimes(2)
|
|
64
|
+
expect(channel.state.addMessageSorted).toHaveBeenCalledWith(msg1.message)
|
|
65
|
+
expect(channel.state.addMessageSorted).toHaveBeenCalledWith(msg2.message)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('does nothing when there are no pending messages', () => {
|
|
69
|
+
const channel = createChannel({ pending_messages: [] })
|
|
70
|
+
|
|
71
|
+
renderHook(() => useRestorePendingMessages(channel))
|
|
72
|
+
|
|
73
|
+
expect(channel.state.addMessageSorted).not.toHaveBeenCalled()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('only restores once per channel even if re-rendered', () => {
|
|
77
|
+
const pendingMsg = {
|
|
78
|
+
message: {
|
|
79
|
+
id: 'msg-1',
|
|
80
|
+
text: 'Hello',
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
const channel = createChannel({ pending_messages: [pendingMsg] })
|
|
84
|
+
|
|
85
|
+
const { rerender } = renderHook(() => useRestorePendingMessages(channel))
|
|
86
|
+
|
|
87
|
+
rerender()
|
|
88
|
+
rerender()
|
|
89
|
+
|
|
90
|
+
expect(channel.state.addMessageSorted).toHaveBeenCalledOnce()
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('restores again when switching to a different channel', () => {
|
|
94
|
+
const pendingMsg = {
|
|
95
|
+
message: {
|
|
96
|
+
id: 'msg-1',
|
|
97
|
+
text: 'Hello',
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
const channel1 = createChannel({
|
|
101
|
+
cid: 'messaging:channel-1',
|
|
102
|
+
pending_messages: [pendingMsg],
|
|
103
|
+
})
|
|
104
|
+
const channel2 = createChannel({
|
|
105
|
+
cid: 'messaging:channel-2',
|
|
106
|
+
pending_messages: [pendingMsg],
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const { rerender } = renderHook(
|
|
110
|
+
({ channel }) => useRestorePendingMessages(channel),
|
|
111
|
+
{ initialProps: { channel: channel1 } }
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
expect(channel1.state.addMessageSorted).toHaveBeenCalledOnce()
|
|
115
|
+
|
|
116
|
+
rerender({ channel: channel2 })
|
|
117
|
+
|
|
118
|
+
expect(channel2.state.addMessageSorted).toHaveBeenCalledOnce()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('handles pending messages with no metadata gracefully', () => {
|
|
122
|
+
const noMetadataMsg = {
|
|
123
|
+
message: { id: 'msg-1', text: 'No metadata' },
|
|
124
|
+
}
|
|
125
|
+
const channel = createChannel({
|
|
126
|
+
pending_messages: [noMetadataMsg],
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
renderHook(() => useRestorePendingMessages(channel))
|
|
130
|
+
|
|
131
|
+
expect(channel.state.addMessageSorted).toHaveBeenCalledOnce()
|
|
132
|
+
expect(channel.state.addMessageSorted).toHaveBeenCalledWith(
|
|
133
|
+
noMetadataMsg.message
|
|
134
|
+
)
|
|
135
|
+
})
|
|
136
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
import type { Channel } from 'stream-chat'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Restores pending messages into the channel's visible message list so they
|
|
6
|
+
* appear as if they were already sent.
|
|
7
|
+
*
|
|
8
|
+
* Stream's pending-messages feature removes messages from the channel view
|
|
9
|
+
* once client state is lost (e.g. page refresh). This hook works around that
|
|
10
|
+
* limitation by reading `channel.state.pending_messages` on channel load and
|
|
11
|
+
* re-inserting them via `channel.state.addMessageSorted`.
|
|
12
|
+
*
|
|
13
|
+
* The restoration runs once per channel (tracked by `channel.cid`).
|
|
14
|
+
*/
|
|
15
|
+
export function useRestorePendingMessages(channel: Channel) {
|
|
16
|
+
const restoredChannelRef = useRef<string | null>(null)
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const cid = channel.cid
|
|
20
|
+
if (!cid || restoredChannelRef.current === cid) return
|
|
21
|
+
|
|
22
|
+
const pendingMessages = channel.state.pending_messages
|
|
23
|
+
if (!pendingMessages?.length) {
|
|
24
|
+
// No pending messages — mark as restored so we don't re-check on
|
|
25
|
+
// re-renders, but still allow a retry if the channel object changes.
|
|
26
|
+
restoredChannelRef.current = cid
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (const pending of pendingMessages) {
|
|
31
|
+
channel.state.addMessageSorted(pending.message)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
restoredChannelRef.current = cid
|
|
35
|
+
}, [channel])
|
|
36
|
+
}
|