@linktr.ee/messaging-react 4.2.0-rc-1787325521 → 4.2.0-rc-1787599724

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 (96) hide show
  1. package/dist/{Card-BYJyA83X.js → Card-D7UPq5-g.js} +2 -2
  2. package/dist/{Card-BYJyA83X.js.map → Card-D7UPq5-g.js.map} +1 -1
  3. package/dist/{Card-BplJtZao.cjs → Card-DcBZqMBt.cjs} +2 -2
  4. package/dist/{Card-BplJtZao.cjs.map → Card-DcBZqMBt.cjs.map} +1 -1
  5. package/dist/assets/index.css +1 -1
  6. package/dist/{index-BUC9vqhJ.js → index-CSkr_CO5.js} +2089 -1952
  7. package/dist/index-CSkr_CO5.js.map +1 -0
  8. package/dist/index-CwkY7QwT.cjs +3 -0
  9. package/dist/index-CwkY7QwT.cjs.map +1 -0
  10. package/dist/index.cjs +1 -1
  11. package/dist/index.d.ts +59 -78
  12. package/dist/index.js +26 -30
  13. package/dist/testing.cjs +1 -1
  14. package/dist/testing.cjs.map +1 -1
  15. package/dist/testing.js +2 -2
  16. package/dist/testing.js.map +1 -1
  17. package/package.json +13 -11
  18. package/src/components/AttachmentCard/utils/icons.ts +7 -6
  19. package/src/components/Avatar/Avatar.test.tsx +0 -126
  20. package/src/components/Avatar/getAvatarEmoji.test.ts +0 -39
  21. package/src/components/ChannelList/CustomChannelPreview.test.tsx +3 -410
  22. package/src/components/ChannelList/index.test.tsx +1 -132
  23. package/src/components/ChannelView.stories.tsx +0 -4
  24. package/src/components/ChannelView.test.tsx +3 -9
  25. package/src/components/CustomMessage/CustomMessage.stories.tsx +314 -6
  26. package/src/components/CustomMessage/CustomMessage.test.tsx +2 -56
  27. package/src/components/CustomMessage/MessageTag.stories.tsx +53 -17
  28. package/src/components/CustomMessage/MessageTag.test.tsx +40 -7
  29. package/src/components/CustomMessage/MessageTag.tsx +39 -7
  30. package/src/components/CustomMessage/SentMessageDeliveryStatus.test.tsx +7 -8
  31. package/src/components/CustomMessage/SentMessageDeliveryStatus.tsx +1 -2
  32. package/src/components/CustomMessage/StreamAttachmentMessage.test.tsx +33 -0
  33. package/src/components/CustomMessage/StreamAttachmentMessage.tsx +12 -5
  34. package/src/components/CustomMessage/index.tsx +19 -22
  35. package/src/components/CustomMessageInput/index.tsx +1 -1
  36. package/src/components/CustomSystemMessage/index.tsx +2 -3
  37. package/src/components/LinkAttachment/LinkAttachment.test.tsx +0 -327
  38. package/src/components/MessageAttachment/Audio/AudioAttachment.stories.tsx +165 -58
  39. package/src/components/MessageAttachment/Audio/WaveformAudioRow.test.tsx +153 -0
  40. package/src/components/MessageAttachment/Audio/WaveformAudioRow.tsx +450 -0
  41. package/src/components/MessageAttachment/Audio/audioRowRequester.test.ts +24 -0
  42. package/src/components/MessageAttachment/Audio/audioRowRequester.ts +16 -0
  43. package/src/components/MessageAttachment/Audio/index.tsx +83 -77
  44. package/src/components/MessageAttachment/MessageAttachment.test.tsx +176 -74
  45. package/src/components/MessageAttachment/_shared/fileMeta.test.ts +0 -44
  46. package/src/components/MessageAttachment/_shared/useIsClient.ts +23 -0
  47. package/src/components/MessageAttachment/index.tsx +25 -9
  48. package/src/components/MessageAttachment/types.ts +19 -9
  49. package/src/components/MessagingShell/MessagingShell.test.tsx +0 -20
  50. package/src/components/MessagingShell/index.tsx +0 -2
  51. package/src/components/RichCard/RichCard.test.tsx +1 -3
  52. package/src/index.ts +0 -4
  53. package/src/providers/MessagingProvider.test.tsx +2 -130
  54. package/src/providers/MessagingProvider.tsx +15 -26
  55. package/src/styles.css +210 -57
  56. package/src/testing/createMockMessagingClient.ts +2 -2
  57. package/src/types.ts +0 -9
  58. package/dist/index-BUC9vqhJ.js.map +0 -1
  59. package/dist/index-BkqyKPJP.cjs +0 -3
  60. package/dist/index-BkqyKPJP.cjs.map +0 -1
  61. package/src/components/AttachmentCard/MediaPlayer.test.tsx +0 -346
  62. package/src/components/AttachmentCard/Thumbnail.test.tsx +0 -215
  63. package/src/components/AttachmentCard/index.test.tsx +0 -160
  64. package/src/components/ChannelList/ChannelListContext.test.tsx +0 -87
  65. package/src/components/CustomMessage/Locked.stories.tsx +0 -108
  66. package/src/components/CustomMessage/PaidMessage.stories.tsx +0 -42
  67. package/src/components/CustomMessage/Tip.stories.tsx +0 -48
  68. package/src/components/CustomMessage/TipMessage/TipMessage.stories.tsx +0 -51
  69. package/src/components/CustomMessage/TipMessage/TipMessage.test.tsx +0 -65
  70. package/src/components/CustomMessage/TipMessage/index.tsx +0 -27
  71. package/src/components/FaqList/FaqList.test.tsx +0 -204
  72. package/src/components/LinkAttachment/components/_shared/BubbleTail.test.tsx +0 -76
  73. package/src/components/LinkAttachment/components/_shared/CardBody.test.tsx +0 -248
  74. package/src/components/LinkAttachment/components/_shared/CardCta.test.tsx +0 -123
  75. package/src/components/LinkAttachment/components/_shared/CardShell.test.tsx +0 -193
  76. package/src/components/LinkAttachment/components/_shared/CardThumbnail.test.tsx +0 -242
  77. package/src/components/LinkAttachment/components/_shared/hex.test.ts +0 -37
  78. package/src/components/LinkAttachment/components/_shared/normalizeExternalHref.test.ts +0 -72
  79. package/src/components/LinkAttachment/components/_shared/useChinPalette.test.ts +0 -162
  80. package/src/components/MessageAttachment/MessageAttachment.behavior.test.tsx +0 -182
  81. package/src/components/MessageAttachment/_shared/Bubble.test.tsx +0 -111
  82. package/src/components/MessageAttachment/_shared/CarouselNav.test.tsx +0 -36
  83. package/src/components/MessageAttachment/_shared/CompactDocumentRow.test.tsx +0 -114
  84. package/src/components/MessageAttachment/_shared/DismissButton.test.tsx +0 -42
  85. package/src/components/MessageAttachment/_shared/DownloadAction.test.tsx +0 -168
  86. package/src/components/MessageAttachment/_shared/ImageViewer.test.tsx +0 -52
  87. package/src/components/MessageAttachment/_shared/MediaStackGrid.test.tsx +0 -69
  88. package/src/components/MessageAttachment/_shared/PdfViewer.test.tsx +0 -45
  89. package/src/components/MessageAttachment/_shared/VideoViewer.test.tsx +0 -55
  90. package/src/components/MessageAttachment/_shared/ViewerShell.test.tsx +0 -246
  91. package/src/components/MessageAttachment/_shared/triggerDownload.test.ts +0 -113
  92. package/src/components/MessageAttachment/_shared/useCarousel.test.tsx +0 -68
  93. package/src/components/MessageAttachment/_shared/viewerChromeClasses.test.ts +0 -16
  94. package/src/logging/index.test.tsx +0 -136
  95. package/src/logging/index.tsx +0 -48
  96. package/src/stories/customMessageStoryTemplate.tsx +0 -202
@@ -1,160 +0,0 @@
1
- import React from 'react'
2
- import { describe, expect, it } from 'vitest'
3
-
4
- import { renderWithProviders, screen } from '../../test/utils'
5
-
6
- import AttachmentCard from './index'
7
-
8
- const thumbnail = <div data-testid="thumbnail">thumbnail</div>
9
-
10
- describe('AttachmentCard', () => {
11
- it('applies the dark and light root surfaces and type icon colors', () => {
12
- const dark = renderWithProviders(
13
- <AttachmentCard
14
- variant="dark"
15
- thumbnail={thumbnail}
16
- mimeType="application/pdf"
17
- title="Dark file"
18
- />
19
- )
20
- expect(screen.getByText('Dark file')).toBeInTheDocument()
21
- expect(dark.container.firstElementChild).toHaveClass('bg-[#1e2330]')
22
- expect(dark.container.querySelector('svg')).toHaveClass('text-white/55')
23
- dark.unmount()
24
-
25
- const light = renderWithProviders(
26
- <AttachmentCard
27
- variant="light"
28
- thumbnail={thumbnail}
29
- mimeType="application/pdf"
30
- title="Light file"
31
- />
32
- )
33
- expect(light.container.firstElementChild).toHaveClass('bg-white')
34
- expect(light.container.querySelector('svg')).toHaveClass('text-black/55')
35
- })
36
-
37
- it('uses the dark placeholder title and dims it when title is absent', () => {
38
- renderWithProviders(
39
- <AttachmentCard
40
- variant="dark"
41
- thumbnail={thumbnail}
42
- mimeType="application/pdf"
43
- placeholderTitle="Waiting for title"
44
- />
45
- )
46
- expect(screen.getByText('Waiting for title')).toHaveClass('text-white/30')
47
- })
48
-
49
- it('does not render a light title for missing or whitespace-only values', () => {
50
- const { rerender } = renderWithProviders(
51
- <AttachmentCard
52
- variant="light"
53
- thumbnail={thumbnail}
54
- mimeType="application/pdf"
55
- />
56
- )
57
- expect(screen.queryByText('Attachment title')).toBeNull()
58
-
59
- rerender(
60
- <AttachmentCard
61
- variant="light"
62
- thumbnail={thumbnail}
63
- mimeType="application/pdf"
64
- title=" "
65
- />
66
- )
67
- expect(screen.queryByText(' ')).toBeNull()
68
- })
69
-
70
- it('uses an undimmed title class when a dark title is provided', () => {
71
- renderWithProviders(
72
- <AttachmentCard
73
- variant="dark"
74
- thumbnail={thumbnail}
75
- mimeType="application/pdf"
76
- title="Named file"
77
- />
78
- )
79
- expect(screen.getByText('Named file')).toHaveClass('text-white')
80
- expect(screen.getByText('Named file')).not.toHaveClass('text-white/30')
81
- })
82
-
83
- it('renders detail only for non-empty values', () => {
84
- const { rerender } = renderWithProviders(
85
- <AttachmentCard
86
- variant="light"
87
- thumbnail={thumbnail}
88
- mimeType="application/pdf"
89
- title="File"
90
- detail="2 MB"
91
- />
92
- )
93
- expect(screen.getByText('2 MB')).toBeInTheDocument()
94
-
95
- rerender(
96
- <AttachmentCard
97
- variant="light"
98
- thumbnail={thumbnail}
99
- mimeType="application/pdf"
100
- title="File"
101
- detail=""
102
- />
103
- )
104
- expect(screen.queryByText('2 MB')).toBeNull()
105
- })
106
-
107
- it('renders optional slots only when supplied', () => {
108
- const { container } = renderWithProviders(
109
- <AttachmentCard
110
- variant="light"
111
- thumbnail={thumbnail}
112
- mimeType="application/pdf"
113
- statusBadge={<span>Ready</span>}
114
- action={<button type="button">Open</button>}
115
- topLeft={<span>Left</span>}
116
- topRight={<span>Right</span>}
117
- />
118
- )
119
- expect(screen.getByText('Ready')).toBeInTheDocument()
120
- expect(screen.getByRole('button', { name: 'Open' })).toBeInTheDocument()
121
- expect(screen.getByText('Left').parentElement).toHaveClass(
122
- 'pointer-events-auto',
123
- 'absolute',
124
- 'left-3',
125
- 'top-3'
126
- )
127
- expect(screen.getByText('Right').parentElement).toHaveClass(
128
- 'pointer-events-auto',
129
- 'absolute',
130
- 'right-3',
131
- 'top-3'
132
- )
133
- expect(container.querySelectorAll('.pointer-events-auto')).toHaveLength(2)
134
-
135
- const withoutSlots = renderWithProviders(
136
- <AttachmentCard
137
- variant="light"
138
- thumbnail={thumbnail}
139
- mimeType="application/pdf"
140
- />
141
- )
142
- expect(
143
- withoutSlots.container.querySelectorAll('.pointer-events-auto')
144
- ).toHaveLength(0)
145
- })
146
-
147
- it('forwards rootRef and data-testid to the root', () => {
148
- const rootRef = React.createRef<HTMLDivElement>()
149
- renderWithProviders(
150
- <AttachmentCard
151
- variant="light"
152
- thumbnail={thumbnail}
153
- mimeType="application/pdf"
154
- rootRef={rootRef}
155
- data-testid="attachment-card"
156
- />
157
- )
158
- expect(screen.getByTestId('attachment-card')).toBe(rootRef.current)
159
- })
160
- })
@@ -1,87 +0,0 @@
1
- import type { Channel } from 'stream-chat'
2
- import { describe, expect, it, vi } from 'vitest'
3
-
4
- import { fireEvent, renderWithProviders, screen } from '../../test/utils'
5
-
6
- import {
7
- ChannelListProvider,
8
- useChannelListContext,
9
- } from './ChannelListContext'
10
-
11
- const channel = { id: 'channel-1' } as Channel
12
-
13
- function ContextConsumer() {
14
- const value = useChannelListContext()
15
-
16
- return (
17
- <div>
18
- <span data-testid="selected-channel">
19
- {value.selectedChannel?.id ?? 'undefined'}
20
- </span>
21
- <span data-testid="debug">{String(value.debug)}</span>
22
- <span data-testid="render-message-preview">
23
- {value.renderMessagePreview ? 'provided' : 'undefined'}
24
- </span>
25
- <span data-testid="channel-preview">
26
- {value.channelPreview ? 'provided' : 'undefined'}
27
- </span>
28
- <span data-testid="viewer-language">
29
- {value.viewerLanguage ?? 'undefined'}
30
- </span>
31
- <button type="button" onClick={() => value.onChannelSelect(channel)}>
32
- Select channel
33
- </button>
34
- </div>
35
- )
36
- }
37
-
38
- describe('ChannelListContext', () => {
39
- it('provides safe documented defaults without a provider', () => {
40
- renderWithProviders(<ContextConsumer />)
41
-
42
- expect(screen.getByTestId('selected-channel')).toHaveTextContent(
43
- 'undefined'
44
- )
45
- expect(screen.getByTestId('debug')).toHaveTextContent('false')
46
- expect(screen.getByTestId('render-message-preview')).toHaveTextContent(
47
- 'undefined'
48
- )
49
- expect(screen.getByTestId('channel-preview')).toHaveTextContent('undefined')
50
- expect(screen.getByTestId('viewer-language')).toHaveTextContent('undefined')
51
- expect(() => fireEvent.click(screen.getByRole('button'))).not.toThrow()
52
- })
53
-
54
- it('exposes the value supplied by a provider', () => {
55
- const onChannelSelect = vi.fn()
56
- const ChannelPreview = () => null
57
- const renderMessagePreview = () => 'preview'
58
-
59
- renderWithProviders(
60
- <ChannelListProvider
61
- value={{
62
- selectedChannel: channel,
63
- onChannelSelect,
64
- debug: true,
65
- renderMessagePreview,
66
- channelPreview: ChannelPreview,
67
- viewerLanguage: 'es',
68
- }}
69
- >
70
- <ContextConsumer />
71
- </ChannelListProvider>
72
- )
73
-
74
- expect(screen.getByTestId('selected-channel')).toHaveTextContent(
75
- 'channel-1'
76
- )
77
- expect(screen.getByTestId('debug')).toHaveTextContent('true')
78
- expect(screen.getByTestId('render-message-preview')).toHaveTextContent(
79
- 'provided'
80
- )
81
- expect(screen.getByTestId('channel-preview')).toHaveTextContent('provided')
82
- expect(screen.getByTestId('viewer-language')).toHaveTextContent('es')
83
-
84
- fireEvent.click(screen.getByRole('button'))
85
- expect(onChannelSelect).toHaveBeenCalledWith(channel)
86
- })
87
- })
@@ -1,108 +0,0 @@
1
- import type { Meta } from '@storybook/react'
2
-
3
- import {
4
- Template,
5
- type TemplateProps,
6
- } from '../../stories/customMessageStoryTemplate'
7
- import {
8
- currentUserArgType,
9
- storyUsers,
10
- } from '../../stories/decorators/storyUser'
11
-
12
- const meta: Meta = {
13
- title: 'CustomMessage/Locked Attachment',
14
- argTypes: currentUserArgType,
15
- args: { currentUser: storyUsers.creator },
16
- parameters: {
17
- layout: 'fullscreen',
18
- },
19
- }
20
- export default meta
21
-
22
- export const LockedAttachment = Template.bind({})
23
- LockedAttachment.args = {
24
- messages: [
25
- {
26
- id: 'msg-1',
27
- text: 'Can I have your workout plan?',
28
- user: storyUsers.visitor,
29
- },
30
- {
31
- id: 'msg-2',
32
- text: 'Yes, of course!',
33
- user: storyUsers.creator,
34
- },
35
- {
36
- id: 'msg-3',
37
- text: 'let me know if you have any questions!',
38
- user: storyUsers.creator,
39
- metadata: {
40
- custom_type: 'MESSAGE_ATTACHMENT',
41
- amount_text: 'AU$9.99',
42
- attachment_title: "Alicia's Workout Plan",
43
- attachment_mime_type: 'video/mp4',
44
- attachment_thumbnail: '/video-thumbnail-blurred.jpg',
45
- attachment_detail: '1:20',
46
- },
47
- },
48
- {
49
- id: 'msg-4',
50
- text: 'Looks amazing, unlocking now!',
51
- user: storyUsers.visitor,
52
- },
53
- ],
54
- } satisfies TemplateProps
55
-
56
- export const LockedTextAttachment = Template.bind({})
57
- LockedTextAttachment.args = {
58
- messages: [
59
- {
60
- id: 'msg-1',
61
- text: 'Can you send me the details privately?',
62
- user: storyUsers.visitor,
63
- },
64
- {
65
- id: 'msg-2',
66
- // Empty text: the real content is redacted server-side until purchase,
67
- // so the bubble renders the blurred placeholder instead.
68
- text: '',
69
- user: storyUsers.creator,
70
- metadata: {
71
- custom_type: 'MESSAGE_ATTACHMENT',
72
- amount_text: 'AU$4.99',
73
- attachment_content_type: 'text',
74
- },
75
- },
76
- {
77
- id: 'msg-3',
78
- text: '',
79
- user: storyUsers.creator,
80
- metadata: {
81
- custom_type: 'MESSAGE_ATTACHMENT',
82
- amount_text: 'AU$4.99',
83
- payment_status: 'paid',
84
- attachment_content_type: 'text',
85
- },
86
- },
87
- {
88
- id: 'msg-4',
89
- // Buyer reveal: backend writes the real text back onto the message.
90
- text: "Here's the address — see you Thursday at 7!",
91
- user: storyUsers.creator,
92
- metadata: {
93
- custom_type: 'MESSAGE_ATTACHMENT',
94
- amount_text: 'AU$4.99',
95
- payment_status: 'paid',
96
- attachment_content_type: 'text',
97
- },
98
- },
99
- ],
100
- } satisfies TemplateProps
101
- LockedTextAttachment.parameters = {
102
- docs: {
103
- description: {
104
- story:
105
- 'A paid text-only message renders as a regular bubble with blurred placeholder copy (msg-2: pending, msg-3: purchased-but-not-yet-revealed) until the real text is revealed (msg-4).',
106
- },
107
- },
108
- }
@@ -1,42 +0,0 @@
1
- import type { Meta } from '@storybook/react'
2
-
3
- import {
4
- Template,
5
- type TemplateProps,
6
- } from '../../stories/customMessageStoryTemplate'
7
- import {
8
- currentUserArgType,
9
- storyUsers,
10
- } from '../../stories/decorators/storyUser'
11
-
12
- const meta: Meta = {
13
- title: 'CustomMessage/Paid Message',
14
- argTypes: currentUserArgType,
15
- args: { currentUser: storyUsers.creator },
16
- parameters: {
17
- layout: 'fullscreen',
18
- },
19
- }
20
- export default meta
21
-
22
- // A paid message keeps the original small "Delivered with $X message" tag
23
- // (gift icon + green text) under the bubble — unlike a tip, it never gets
24
- // the pill treatment.
25
- export const Default = Template.bind({})
26
- Default.args = {
27
- messages: [
28
- {
29
- id: 'msg-1',
30
- text: "Here's the private coaching link we discussed!",
31
- user: storyUsers.creator,
32
- metadata: { custom_type: 'MESSAGE_PAID', amount_text: '$10' },
33
- },
34
- // Trailing reply so the paid message is no longer the latest own
35
- // message — keeps the "✓✓ Delivered" stamp off this example.
36
- {
37
- id: 'msg-2',
38
- text: 'Thank you so much!',
39
- user: storyUsers.visitor,
40
- },
41
- ],
42
- } satisfies TemplateProps
@@ -1,48 +0,0 @@
1
- import type { Meta } from '@storybook/react'
2
-
3
- import {
4
- Template,
5
- type TemplateProps,
6
- } from '../../stories/customMessageStoryTemplate'
7
- import {
8
- currentUserArgType,
9
- storyUsers,
10
- } from '../../stories/decorators/storyUser'
11
-
12
- const meta: Meta = {
13
- title: 'CustomMessage/Tip Message',
14
- argTypes: currentUserArgType,
15
- args: { currentUser: storyUsers.creator },
16
- parameters: {
17
- layout: 'fullscreen',
18
- },
19
- }
20
- export default meta
21
-
22
- // Figma "Tip with message" (node 2353:14548): a sent tip pill stacked
23
- // directly above its accompanying text bubble, tail + delivery status below.
24
- export const TipWithMessage = Template.bind({})
25
- TipWithMessage.args = {
26
- messages: [
27
- {
28
- id: 'msg-1',
29
- text: 'Thanks you so much!',
30
- user: storyUsers.creator,
31
- metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$1' },
32
- },
33
- ],
34
- } satisfies TemplateProps
35
-
36
- // Same "Tip with message" layout, but textless — only the tip pill renders,
37
- // no second bubble.
38
- export const TipWithoutMessage = Template.bind({})
39
- TipWithoutMessage.args = {
40
- messages: [
41
- {
42
- id: 'msg-1',
43
- text: '',
44
- user: storyUsers.creator,
45
- metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$1' },
46
- },
47
- ],
48
- } satisfies TemplateProps
@@ -1,51 +0,0 @@
1
- import type { Meta, StoryFn } from '@storybook/react'
2
- import React from 'react'
3
- import { LocalMessage } from 'stream-chat'
4
-
5
- import { now } from '../../../stories/decorators/storyTime'
6
-
7
- import { TipMessage } from '.'
8
-
9
- type ComponentProps = React.ComponentProps<typeof TipMessage>
10
-
11
- const meta: Meta<ComponentProps> = {
12
- title: 'TipMessage',
13
- component: TipMessage,
14
- parameters: {
15
- layout: 'centered',
16
- },
17
- }
18
- export default meta
19
-
20
- interface MockMessageOptions {
21
- metadata?: {
22
- custom_type?: 'MESSAGE_TIP'
23
- amount_text?: string
24
- }
25
- text?: string
26
- }
27
-
28
- const createMockMessage = (options?: MockMessageOptions): LocalMessage =>
29
- ({
30
- id: 'msg-1',
31
- text: options?.text ?? '',
32
- type: 'regular',
33
- created_at: now(),
34
- updated_at: now(),
35
- metadata: options?.metadata,
36
- }) as LocalMessage
37
-
38
- const Template: StoryFn<ComponentProps> = (args) => {
39
- return (
40
- <div className="p-12">
41
- <TipMessage {...args} />
42
- </div>
43
- )
44
- }
45
-
46
- export const Standalone: StoryFn<ComponentProps> = Template.bind({})
47
- Standalone.args = {
48
- message: createMockMessage({
49
- metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$50.00' },
50
- }),
51
- }
@@ -1,65 +0,0 @@
1
- import { LocalMessage } from 'stream-chat'
2
- import { describe, expect, it } from 'vitest'
3
-
4
- import { renderWithProviders, screen } from '../../../test/utils'
5
-
6
- import { TipMessage } from '.'
7
-
8
- interface MockMessageOptions {
9
- metadata?: {
10
- custom_type?: 'MESSAGE_TIP'
11
- amount_text?: string
12
- }
13
- text?: string
14
- }
15
-
16
- const createMockMessage = (options?: MockMessageOptions): LocalMessage =>
17
- ({
18
- id: 'msg-1',
19
- text: options?.text ?? 'Hello world',
20
- type: 'regular',
21
- created_at: new Date(),
22
- updated_at: new Date(),
23
- metadata: options?.metadata,
24
- }) as LocalMessage
25
-
26
- describe('TipMessage', () => {
27
- it('renders tip pill regardless of accompanying text', () => {
28
- renderWithProviders(
29
- <TipMessage
30
- message={createMockMessage({
31
- metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$5.50' },
32
- })}
33
- />
34
- )
35
-
36
- const tipTag = screen.getByText('$5.50 tip!')
37
- expect(tipTag.closest('.message-tip-pill')).not.toBeNull()
38
- })
39
-
40
- it('renders tip pill label for textless tip messages', () => {
41
- renderWithProviders(
42
- <TipMessage
43
- message={createMockMessage({
44
- text: '',
45
- metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$50.00' },
46
- })}
47
- />
48
- )
49
-
50
- const tipTag = screen.getByText('$50.00 tip!')
51
- expect(tipTag.closest('.message-tip-pill')).not.toBeNull()
52
- })
53
-
54
- it('returns null when amount_text is missing', () => {
55
- const { container } = renderWithProviders(
56
- <TipMessage
57
- message={createMockMessage({
58
- metadata: { custom_type: 'MESSAGE_TIP' },
59
- })}
60
- />
61
- )
62
-
63
- expect(container.firstChild).toBeNull()
64
- })
65
- })
@@ -1,27 +0,0 @@
1
- import { MESSAGE_CUSTOM_TYPE } from '@linktr.ee/messaging-taxonomy'
2
- import { LocalMessage } from 'stream-chat'
3
-
4
- /** Check if a message is a tip based on metadata */
5
- export const isTipMessage = (message: LocalMessage): boolean => {
6
- return message.metadata?.custom_type === MESSAGE_CUSTOM_TYPE.TIP
7
- }
8
-
9
- /** Tip pill — the gift-emoji bubble a tip message attaches, with or without accompanying text */
10
- export const TipMessage = ({ message }: { message: LocalMessage }) => {
11
- const amountText = message.metadata?.amount_text
12
- if (!amountText) return null
13
-
14
- return (
15
- <div className="message-tip-pill inline-flex items-end gap-0.5 max-w-[520px] rounded-[1.5rem] bg-[#43e660] px-4 py-3 text-white">
16
- <span
17
- className="flex h-5 items-center text-[20px] leading-none"
18
- aria-hidden="true"
19
- >
20
- 🎁
21
- </span>
22
- <span className="text-[14px] font-bold leading-[1.43] tracking-[0.28px] text-[rgba(4,4,3,0.9)]">
23
- {amountText} tip!
24
- </span>
25
- </div>
26
- )
27
- }