@linktr.ee/messaging-react 1.24.2 → 1.24.3-rc-1773288966

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/messaging-react",
3
- "version": "1.24.2",
3
+ "version": "1.24.3-rc-1773288966",
4
4
  "description": "React messaging components built on messaging-core for web applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,147 @@
1
+ import React from 'react'
2
+ import type { Channel } from 'stream-chat'
3
+ import { describe, expect, it, vi, beforeEach } from 'vitest'
4
+
5
+ import { renderWithProviders, screen } from '../test/utils'
6
+
7
+ import { ChannelView } from './ChannelView'
8
+
9
+ let activeChannel: unknown
10
+
11
+ vi.mock('stream-chat-react', () => ({
12
+ Channel: ({
13
+ channel,
14
+ children,
15
+ }: {
16
+ channel: unknown
17
+ children: React.ReactNode
18
+ }) => {
19
+ activeChannel = channel
20
+ return <div data-testid="channel">{children}</div>
21
+ },
22
+ Window: ({ children }: { children: React.ReactNode }) => (
23
+ <div data-testid="window">{children}</div>
24
+ ),
25
+ MessageList: () => <div data-testid="message-list" />,
26
+ WithComponents: ({ children }: { children: React.ReactNode }) => (
27
+ <>{children}</>
28
+ ),
29
+ useMessageContext: () => ({ message: { id: 'message-1', text: 'hello' } }),
30
+ useChannelStateContext: () => ({ channel: activeChannel }),
31
+ }))
32
+
33
+ vi.mock('../providers/MessagingProvider', () => ({
34
+ useMessagingContext: () => ({ service: null, debug: false }),
35
+ }))
36
+
37
+ vi.mock('./CustomMessageInput', () => ({
38
+ CustomMessageInput: ({
39
+ renderActions,
40
+ }: {
41
+ renderActions?: () => React.ReactNode
42
+ }) => <div data-testid="message-input">{renderActions?.()}</div>,
43
+ }))
44
+
45
+ vi.mock('./CustomMessage', () => ({
46
+ CustomMessage: () => <div data-testid="custom-message" />,
47
+ }))
48
+
49
+ vi.mock('./CustomSystemMessage', () => ({
50
+ CustomSystemMessage: () => <div data-testid="custom-system-message" />,
51
+ }))
52
+
53
+ vi.mock('./CustomDateSeparator', () => ({
54
+ CustomDateSeparator: () => <div data-testid="custom-date-separator" />,
55
+ }))
56
+
57
+ vi.mock('./Avatar', () => ({
58
+ Avatar: () => <div data-testid="avatar" />,
59
+ }))
60
+
61
+ const createChannel = () =>
62
+ ({
63
+ id: 'channel-1',
64
+ cid: 'messaging:channel-1',
65
+ data: {},
66
+ _client: { userID: 'visitor-1' },
67
+ state: {
68
+ members: {
69
+ visitor: { user: { id: 'visitor-1', name: 'Visitor' }, role: 'owner' },
70
+ linker: { user: { id: 'linker-1', name: 'Linker' }, role: 'member' },
71
+ },
72
+ membership: {},
73
+ messages: [],
74
+ },
75
+ on: vi.fn(),
76
+ off: vi.fn(),
77
+ sendMessage: vi.fn(),
78
+ hide: vi.fn(),
79
+ pin: vi.fn(),
80
+ unpin: vi.fn(),
81
+ }) as unknown as Channel
82
+
83
+ describe('ChannelView', () => {
84
+ beforeEach(() => {
85
+ activeChannel = undefined
86
+ })
87
+
88
+ it('renders conversation footer between message list and message input', () => {
89
+ const channel = createChannel()
90
+ const renderConversationFooter = vi.fn((currentChannel: Channel) => (
91
+ <div data-testid="conversation-footer">
92
+ footer-{currentChannel.id}
93
+ </div>
94
+ ))
95
+
96
+ renderWithProviders(
97
+ <ChannelView
98
+ channel={channel}
99
+ renderConversationFooter={renderConversationFooter}
100
+ />
101
+ )
102
+
103
+ const messageList = screen.getByTestId('message-list')
104
+ const conversationFooter = screen.getByTestId('conversation-footer')
105
+ const messageInput = screen.getByTestId('message-input')
106
+
107
+ expect(renderConversationFooter).toHaveBeenCalledWith(channel)
108
+ expect(conversationFooter).toHaveTextContent('footer-channel-1')
109
+ expect(
110
+ messageList.compareDocumentPosition(conversationFooter) &
111
+ Node.DOCUMENT_POSITION_FOLLOWING
112
+ ).toBeTruthy()
113
+ expect(
114
+ conversationFooter.compareDocumentPosition(messageInput) &
115
+ Node.DOCUMENT_POSITION_FOLLOWING
116
+ ).toBeTruthy()
117
+ })
118
+
119
+ it('does not render conversation footer when no renderer is provided', () => {
120
+ renderWithProviders(<ChannelView channel={createChannel()} />)
121
+
122
+ expect(screen.queryByTestId('conversation-footer')).not.toBeInTheDocument()
123
+ })
124
+
125
+ it('keeps channel banner and input action renderers working', () => {
126
+ const channel = createChannel()
127
+ const renderMessageInputActions = vi.fn((currentChannel: Channel) => (
128
+ <button data-testid="message-input-action">{currentChannel.id}</button>
129
+ ))
130
+
131
+ renderWithProviders(
132
+ <ChannelView
133
+ channel={channel}
134
+ renderChannelBanner={() => (
135
+ <div data-testid="channel-banner">channel-banner</div>
136
+ )}
137
+ renderMessageInputActions={renderMessageInputActions}
138
+ />
139
+ )
140
+
141
+ expect(screen.getByTestId('channel-banner')).toBeInTheDocument()
142
+ expect(screen.getByTestId('message-input-action')).toHaveTextContent(
143
+ 'channel-1'
144
+ )
145
+ expect(renderMessageInputActions).toHaveBeenCalledWith(channel)
146
+ })
147
+ })
@@ -524,6 +524,7 @@ const ChannelViewInner: React.FC<{
524
524
  onBack?: () => void
525
525
  showBackButton: boolean
526
526
  renderMessageInputActions?: (channel: ChannelType) => React.ReactNode
527
+ renderConversationFooter?: (channel: ChannelType) => React.ReactNode
527
528
  onLeaveConversation?: (channel: ChannelType) => void
528
529
  onBlockParticipant?: (participantId?: string) => void
529
530
  CustomChannelEmptyState?: React.ComponentType
@@ -543,6 +544,7 @@ const ChannelViewInner: React.FC<{
543
544
  onBack,
544
545
  showBackButton,
545
546
  renderMessageInputActions,
547
+ renderConversationFooter,
546
548
  onLeaveConversation,
547
549
  onBlockParticipant,
548
550
  showDeleteConversation = true,
@@ -640,6 +642,8 @@ const ChannelViewInner: React.FC<{
640
642
  />
641
643
  </div>
642
644
 
645
+ {renderConversationFooter?.(channel)}
646
+
643
647
  {/* Message Input */}
644
648
  <CustomMessageInput
645
649
  renderActions={() => renderMessageInputActions?.(channel)}
@@ -675,6 +679,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
675
679
  onBack,
676
680
  showBackButton = false,
677
681
  renderMessageInputActions,
682
+ renderConversationFooter,
678
683
  onLeaveConversation,
679
684
  onBlockParticipant,
680
685
  className,
@@ -755,6 +760,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
755
760
  onBack={onBack}
756
761
  showBackButton={showBackButton}
757
762
  renderMessageInputActions={renderMessageInputActions}
763
+ renderConversationFooter={renderConversationFooter}
758
764
  onLeaveConversation={onLeaveConversation}
759
765
  onBlockParticipant={onBlockParticipant}
760
766
  CustomChannelEmptyState={CustomChannelEmptyState}
@@ -19,6 +19,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
19
19
  capabilities = {},
20
20
  className,
21
21
  renderMessageInputActions,
22
+ renderConversationFooter,
22
23
  onChannelSelect,
23
24
  onParticipantSelect,
24
25
  initialParticipantFilter,
@@ -484,6 +485,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
484
485
  onBack={handleBackToChannelList}
485
486
  showBackButton={!directConversationMode}
486
487
  renderMessageInputActions={renderMessageInputActions}
488
+ renderConversationFooter={renderConversationFooter}
487
489
  renderChannelBanner={renderChannelBanner}
488
490
  onLeaveConversation={handleLeaveConversation}
489
491
  onBlockParticipant={handleBlockParticipant}
package/src/types.ts CHANGED
@@ -88,6 +88,7 @@ export interface ChannelViewProps {
88
88
  onBack?: () => void
89
89
  showBackButton?: boolean
90
90
  renderMessageInputActions?: (channel: Channel) => React.ReactNode
91
+ renderConversationFooter?: (channel: Channel) => React.ReactNode
91
92
  onLeaveConversation?: (channel: Channel) => void
92
93
  onBlockParticipant?: (participantId?: string) => void
93
94
  className?: string
@@ -192,6 +193,7 @@ export interface ChannelViewProps {
192
193
  export type ChannelViewPassthroughProps = Pick<
193
194
  ChannelViewProps,
194
195
  | 'renderMessageInputActions'
196
+ | 'renderConversationFooter'
195
197
  | 'CustomChannelEmptyState'
196
198
  | 'onDeleteConversationClick'
197
199
  | 'onBlockParticipantClick'