@linktr.ee/messaging-react 1.33.1 → 1.33.2-rc-1777444067
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/dist/{Card-Ddi8bg90.js → Card-CQYlmwhm.js} +2 -2
- package/dist/{Card-Ddi8bg90.js.map → Card-CQYlmwhm.js.map} +1 -1
- package/dist/{Card-DEe10CiS.js → Card-V-Et3tc4.js} +2 -2
- package/dist/{Card-DEe10CiS.js.map → Card-V-Et3tc4.js.map} +1 -1
- package/dist/assets/index.css +1 -1
- package/dist/{index-BePLvyvi.js → index-CahzrNJz.js} +1363 -1127
- package/dist/{index-BePLvyvi.js.map → index-CahzrNJz.js.map} +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/components/LockedAttachment/components/MediaPlayer.tsx +10 -1
- package/src/components/MediaMessage/MediaMessage.stories.tsx +82 -19
- package/src/components/MediaMessage/MediaMessage.test.tsx +277 -18
- package/src/components/MediaMessage/index.tsx +388 -77
- package/src/providers/MessagingProvider.stories.tsx +214 -0
- package/src/providers/MessagingProvider.test.tsx +126 -0
- package/src/styles.css +53 -0
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as e, a as t, C as i, b as n, c as o, d as g, F as r, e as m, L as u, f as M, h as c, i as l, j as h, P as C, k as P, u as d, l as L, m as p, n as v } from "./index-
|
|
1
|
+
import { A as e, a as t, C as i, b as n, c as o, d as g, F as r, e as m, L as u, f as M, h as c, i as l, j as h, P as C, k as P, u as d, l as L, m as p, n as v } from "./index-CahzrNJz.js";
|
|
2
2
|
export {
|
|
3
3
|
e as ActionButton,
|
|
4
4
|
t as Avatar,
|
package/package.json
CHANGED
|
@@ -31,6 +31,12 @@ export interface MediaPlayerProps {
|
|
|
31
31
|
showProgress?: boolean
|
|
32
32
|
/** When true, requests muted playback (helps autoplay policies on video). */
|
|
33
33
|
muted?: boolean
|
|
34
|
+
/**
|
|
35
|
+
* When provided, overrides the default click-to-play-toggle behaviour on the
|
|
36
|
+
* player container. The play/pause button (which calls stopPropagation) is
|
|
37
|
+
* unaffected, so inline playback still works.
|
|
38
|
+
*/
|
|
39
|
+
onContainerClick?: (e: React.MouseEvent) => void
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
const MediaPlayer: React.FC<MediaPlayerProps> = ({
|
|
@@ -43,6 +49,7 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
|
|
|
43
49
|
controls = true,
|
|
44
50
|
showProgress = false,
|
|
45
51
|
muted = false,
|
|
52
|
+
onContainerClick,
|
|
46
53
|
}) => {
|
|
47
54
|
// --- Derived ---
|
|
48
55
|
const sourceType = getSourceType(mimeType)
|
|
@@ -185,13 +192,15 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
|
|
|
185
192
|
tabIndex={0}
|
|
186
193
|
className={`relative cursor-pointer overflow-hidden bg-black ${aspectClass}`}
|
|
187
194
|
style={aspectStyle}
|
|
188
|
-
onClick={() => {
|
|
195
|
+
onClick={(e) => {
|
|
196
|
+
if (onContainerClick) { onContainerClick(e); return }
|
|
189
197
|
if (manualPlayRequired) return
|
|
190
198
|
if (controls) setPlaying((p) => !p)
|
|
191
199
|
}}
|
|
192
200
|
onKeyDown={(e) => {
|
|
193
201
|
if (e.key !== 'Enter' && e.key !== ' ') return
|
|
194
202
|
e.preventDefault()
|
|
203
|
+
if (onContainerClick) { onContainerClick(e as unknown as React.MouseEvent); return }
|
|
195
204
|
if (manualPlayRequired) return
|
|
196
205
|
if (controls) setPlaying((p) => !p)
|
|
197
206
|
}}
|
|
@@ -84,6 +84,43 @@ const VARIANTS = [
|
|
|
84
84
|
},
|
|
85
85
|
] as const
|
|
86
86
|
|
|
87
|
+
const LINK_VARIANTS = [
|
|
88
|
+
{
|
|
89
|
+
label: 'With image',
|
|
90
|
+
attachment: {
|
|
91
|
+
type: 'link',
|
|
92
|
+
og_scrape_url: 'https://linktr.ee/brieparsons',
|
|
93
|
+
title: 'World Famous 3 Bottle Grey Wash Set',
|
|
94
|
+
text: 'When its time to shade, the World Famous Grey Wash set has you covered.',
|
|
95
|
+
image_url: 'https://picsum.photos/seed/linkcard/560/315',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
label: 'No image',
|
|
100
|
+
attachment: {
|
|
101
|
+
type: 'link',
|
|
102
|
+
og_scrape_url: 'https://linktr.ee/brieparsons',
|
|
103
|
+
title: 'World Famous 3 Bottle Grey Wash Set',
|
|
104
|
+
text: 'When its time to shade, the World Famous Grey Wash set has you covered.',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
label: 'Title only',
|
|
109
|
+
attachment: {
|
|
110
|
+
type: 'link',
|
|
111
|
+
og_scrape_url: 'https://linktr.ee/someone',
|
|
112
|
+
title: 'Check out my Linktree',
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
label: 'URL only',
|
|
117
|
+
attachment: {
|
|
118
|
+
type: 'link',
|
|
119
|
+
og_scrape_url: 'https://linktr.ee/someone',
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
] as const
|
|
123
|
+
|
|
87
124
|
// ---------------------------------------------------------------------------
|
|
88
125
|
// Layout primitives
|
|
89
126
|
// ---------------------------------------------------------------------------
|
|
@@ -94,11 +131,10 @@ const GridTable: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
|
94
131
|
</div>
|
|
95
132
|
)
|
|
96
133
|
|
|
97
|
-
const GridHead = () => (
|
|
134
|
+
const GridHead: React.FC<{ labels: readonly string[] }> = ({ labels }) => (
|
|
98
135
|
<thead>
|
|
99
136
|
<tr>
|
|
100
|
-
|
|
101
|
-
{VARIANTS.map(({ label }) => (
|
|
137
|
+
{labels.map((label) => (
|
|
102
138
|
<th key={label} className="text-left text-xs font-medium text-black/40 pb-2">
|
|
103
139
|
{label}
|
|
104
140
|
</th>
|
|
@@ -107,22 +143,15 @@ const GridHead = () => (
|
|
|
107
143
|
</thead>
|
|
108
144
|
)
|
|
109
145
|
|
|
110
|
-
const RowLabel: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
111
|
-
<td className="text-xs text-right font-medium text-black/40 pr-4 align-top pt-2">
|
|
112
|
-
{children}
|
|
113
|
-
</td>
|
|
114
|
-
)
|
|
115
|
-
|
|
116
146
|
// ---------------------------------------------------------------------------
|
|
117
147
|
// Stories
|
|
118
148
|
// ---------------------------------------------------------------------------
|
|
119
149
|
|
|
120
|
-
export const
|
|
150
|
+
export const Received: StoryFn = () => (
|
|
121
151
|
<GridTable>
|
|
122
|
-
<GridHead />
|
|
152
|
+
<GridHead labels={VARIANTS.map((v) => v.label)} />
|
|
123
153
|
<tbody>
|
|
124
154
|
<tr>
|
|
125
|
-
<RowLabel>Sent</RowLabel>
|
|
126
155
|
{VARIANTS.map(({ label, attachment }) => (
|
|
127
156
|
<td key={label} className="align-top">
|
|
128
157
|
<MediaMessage
|
|
@@ -135,20 +164,19 @@ export const Visitor: StoryFn = () => (
|
|
|
135
164
|
</tbody>
|
|
136
165
|
</GridTable>
|
|
137
166
|
)
|
|
138
|
-
|
|
167
|
+
Received.parameters = {
|
|
139
168
|
docs: {
|
|
140
169
|
description: {
|
|
141
|
-
story: '
|
|
170
|
+
story: 'Received messages — left-aligned with avatar, light gray card background.',
|
|
142
171
|
},
|
|
143
172
|
},
|
|
144
173
|
}
|
|
145
174
|
|
|
146
|
-
export const
|
|
175
|
+
export const Sent: StoryFn = () => (
|
|
147
176
|
<GridTable>
|
|
148
|
-
<GridHead />
|
|
177
|
+
<GridHead labels={VARIANTS.map((v) => v.label)} />
|
|
149
178
|
<tbody>
|
|
150
179
|
<tr>
|
|
151
|
-
<RowLabel>Sent</RowLabel>
|
|
152
180
|
{VARIANTS.map(({ label, attachment }) => (
|
|
153
181
|
<td key={label} className="align-top">
|
|
154
182
|
<MediaMessage
|
|
@@ -161,10 +189,45 @@ export const Creator: StoryFn = () => (
|
|
|
161
189
|
</tbody>
|
|
162
190
|
</GridTable>
|
|
163
191
|
)
|
|
164
|
-
|
|
192
|
+
Sent.parameters = {
|
|
193
|
+
docs: {
|
|
194
|
+
description: {
|
|
195
|
+
story: 'Sent messages — right-aligned, no avatar, black card background.',
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export const Links: StoryFn = () => (
|
|
201
|
+
<GridTable>
|
|
202
|
+
<GridHead labels={LINK_VARIANTS.map((v) => v.label)} />
|
|
203
|
+
<tbody>
|
|
204
|
+
<tr>
|
|
205
|
+
{LINK_VARIANTS.map(({ label, attachment }) => (
|
|
206
|
+
<td key={label} className="align-top">
|
|
207
|
+
<MediaMessage
|
|
208
|
+
isMyMessage={false}
|
|
209
|
+
message={base({ user: SENDER, attachments: [attachment as LocalMessage['attachments'][number]] })}
|
|
210
|
+
/>
|
|
211
|
+
</td>
|
|
212
|
+
))}
|
|
213
|
+
</tr>
|
|
214
|
+
<tr>
|
|
215
|
+
{LINK_VARIANTS.map(({ label, attachment }) => (
|
|
216
|
+
<td key={label} className="align-top">
|
|
217
|
+
<MediaMessage
|
|
218
|
+
isMyMessage={true}
|
|
219
|
+
message={base({ user: ME, attachments: [attachment as LocalMessage['attachments'][number]] })}
|
|
220
|
+
/>
|
|
221
|
+
</td>
|
|
222
|
+
))}
|
|
223
|
+
</tr>
|
|
224
|
+
</tbody>
|
|
225
|
+
</GridTable>
|
|
226
|
+
)
|
|
227
|
+
Links.parameters = {
|
|
165
228
|
docs: {
|
|
166
229
|
description: {
|
|
167
|
-
story: '
|
|
230
|
+
story: 'Link preview cards — top row received, bottom row sent. Shows thumbnail, title, description, and URL with image fallback.',
|
|
168
231
|
},
|
|
169
232
|
},
|
|
170
233
|
}
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import type { LocalMessage } from 'stream-chat'
|
|
3
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { beforeAll, describe, expect, it, vi } from 'vitest'
|
|
4
4
|
|
|
5
|
-
import { renderWithProviders, screen } from '../../test/utils'
|
|
5
|
+
import { renderWithProviders, screen, fireEvent } from '../../test/utils'
|
|
6
6
|
|
|
7
7
|
import { MediaMessage } from '.'
|
|
8
8
|
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
HTMLDialogElement.prototype.showModal = vi.fn(function (this: HTMLDialogElement) {
|
|
11
|
+
this.setAttribute('open', '')
|
|
12
|
+
})
|
|
13
|
+
HTMLDialogElement.prototype.close = vi.fn(function (this: HTMLDialogElement) {
|
|
14
|
+
this.removeAttribute('open')
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
|
|
9
18
|
vi.mock('../Avatar', () => ({
|
|
10
19
|
Avatar: ({ id }: { id: string }) => (
|
|
11
20
|
<div data-testid="avatar" data-user-id={id} />
|
|
@@ -16,14 +25,20 @@ vi.mock('../LockedAttachment/components/MediaPlayer', () => ({
|
|
|
16
25
|
default: ({
|
|
17
26
|
source,
|
|
18
27
|
mimeType,
|
|
28
|
+
onContainerClick,
|
|
19
29
|
}: {
|
|
20
30
|
source: string
|
|
21
31
|
mimeType: string
|
|
32
|
+
onContainerClick?: (e: React.MouseEvent) => void
|
|
22
33
|
}) => (
|
|
23
34
|
<div
|
|
35
|
+
role="button"
|
|
36
|
+
tabIndex={0}
|
|
24
37
|
data-testid="media-player"
|
|
25
38
|
data-source={source}
|
|
26
39
|
data-mime-type={mimeType}
|
|
40
|
+
onClick={onContainerClick}
|
|
41
|
+
onKeyDown={undefined}
|
|
27
42
|
/>
|
|
28
43
|
),
|
|
29
44
|
}))
|
|
@@ -41,7 +56,6 @@ vi.mock('../LockedAttachment/utils/mimeType', () => ({
|
|
|
41
56
|
},
|
|
42
57
|
}))
|
|
43
58
|
|
|
44
|
-
// Cast through unknown to avoid satisfying every optional field of LocalMessage
|
|
45
59
|
const msg = (overrides: Record<string, unknown> = {}): LocalMessage =>
|
|
46
60
|
({
|
|
47
61
|
id: 'msg-1',
|
|
@@ -63,7 +77,7 @@ describe('MediaMessage', () => {
|
|
|
63
77
|
expect(container.firstChild).toBeNull()
|
|
64
78
|
})
|
|
65
79
|
|
|
66
|
-
it('renders MediaPlayer for a video attachment', () => {
|
|
80
|
+
it('renders MediaPlayer immediately for a video attachment', () => {
|
|
67
81
|
renderWithProviders(
|
|
68
82
|
<MediaMessage
|
|
69
83
|
message={msg({
|
|
@@ -72,6 +86,7 @@ describe('MediaMessage', () => {
|
|
|
72
86
|
type: 'video',
|
|
73
87
|
asset_url: 'https://cdn.example.com/clip.mp4',
|
|
74
88
|
mime_type: 'video/mp4',
|
|
89
|
+
title: 'My Clip',
|
|
75
90
|
},
|
|
76
91
|
],
|
|
77
92
|
})}
|
|
@@ -80,11 +95,28 @@ describe('MediaMessage', () => {
|
|
|
80
95
|
|
|
81
96
|
const player = screen.getByTestId('media-player')
|
|
82
97
|
expect(player).toBeInTheDocument()
|
|
83
|
-
expect(player).toHaveAttribute(
|
|
84
|
-
|
|
85
|
-
|
|
98
|
+
expect(player).toHaveAttribute('data-source', 'https://cdn.example.com/clip.mp4')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('opens viewer when the video player container is clicked', () => {
|
|
102
|
+
renderWithProviders(
|
|
103
|
+
<MediaMessage
|
|
104
|
+
message={msg({
|
|
105
|
+
attachments: [
|
|
106
|
+
{
|
|
107
|
+
type: 'video',
|
|
108
|
+
asset_url: 'https://cdn.example.com/clip.mp4',
|
|
109
|
+
mime_type: 'video/mp4',
|
|
110
|
+
title: 'My Clip',
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
})}
|
|
114
|
+
/>
|
|
86
115
|
)
|
|
87
|
-
|
|
116
|
+
|
|
117
|
+
fireEvent.click(screen.getByTestId('media-player'))
|
|
118
|
+
expect(HTMLDialogElement.prototype.showModal).toHaveBeenCalled()
|
|
119
|
+
expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument()
|
|
88
120
|
})
|
|
89
121
|
|
|
90
122
|
it('renders MediaPlayer for an audio attachment', () => {
|
|
@@ -108,7 +140,27 @@ describe('MediaMessage', () => {
|
|
|
108
140
|
)
|
|
109
141
|
})
|
|
110
142
|
|
|
111
|
-
it('
|
|
143
|
+
it('does not show viewer or expand button for audio', () => {
|
|
144
|
+
renderWithProviders(
|
|
145
|
+
<MediaMessage
|
|
146
|
+
message={msg({
|
|
147
|
+
attachments: [
|
|
148
|
+
{
|
|
149
|
+
type: 'audio',
|
|
150
|
+
asset_url: 'https://cdn.example.com/track.mp3',
|
|
151
|
+
mime_type: 'audio/mpeg',
|
|
152
|
+
title: 'My Track',
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
})}
|
|
156
|
+
/>
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
expect(screen.queryByRole('button', { name: 'View full screen' })).not.toBeInTheDocument()
|
|
160
|
+
expect(screen.queryByRole('button', { name: 'Close' })).not.toBeInTheDocument()
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('renders an img for an image attachment and opens viewer on click', () => {
|
|
112
164
|
renderWithProviders(
|
|
113
165
|
<MediaMessage
|
|
114
166
|
message={msg({
|
|
@@ -126,9 +178,36 @@ describe('MediaMessage', () => {
|
|
|
126
178
|
|
|
127
179
|
const image = screen.getByRole('img', { name: 'My Photo' })
|
|
128
180
|
expect(image).toHaveAttribute('src', 'https://cdn.example.com/photo.jpg')
|
|
181
|
+
|
|
182
|
+
// Clicking the image button opens the viewer
|
|
183
|
+
fireEvent.click(screen.getByRole('button', { name: 'My Photo' }))
|
|
184
|
+
expect(HTMLDialogElement.prototype.showModal).toHaveBeenCalled()
|
|
185
|
+
const viewerImages = screen.getAllByRole('img', { name: 'My Photo' })
|
|
186
|
+
expect(viewerImages.some((img) => img.classList.contains('rounded-2xl'))).toBe(true)
|
|
129
187
|
})
|
|
130
188
|
|
|
131
|
-
it('
|
|
189
|
+
it('does not show expand button in chin for images, only download', () => {
|
|
190
|
+
renderWithProviders(
|
|
191
|
+
<MediaMessage
|
|
192
|
+
message={msg({
|
|
193
|
+
attachments: [
|
|
194
|
+
{
|
|
195
|
+
type: 'image',
|
|
196
|
+
image_url: 'https://cdn.example.com/photo.jpg',
|
|
197
|
+
mime_type: 'image/jpeg',
|
|
198
|
+
title: 'My Photo',
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
})}
|
|
202
|
+
/>
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
// Thumbnail button + download button — no separate expand/fullscreen button
|
|
206
|
+
expect(screen.queryByRole('button', { name: 'View full screen' })).not.toBeInTheDocument()
|
|
207
|
+
expect(screen.getByRole('button', { name: 'Download' })).toBeInTheDocument()
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('renders PDF card with a viewer button', () => {
|
|
132
211
|
renderWithProviders(
|
|
133
212
|
<MediaMessage
|
|
134
213
|
message={msg({
|
|
@@ -144,13 +223,56 @@ describe('MediaMessage', () => {
|
|
|
144
223
|
/>
|
|
145
224
|
)
|
|
146
225
|
|
|
147
|
-
|
|
148
|
-
expect(link).toHaveAttribute('href', 'https://cdn.example.com/report.pdf')
|
|
226
|
+
expect(screen.getByRole('button', { name: 'Open PDF viewer' })).toBeInTheDocument()
|
|
149
227
|
expect(screen.getByText('Annual Report')).toBeInTheDocument()
|
|
150
|
-
expect(screen.getByTestId('type-icon')).toBeInTheDocument()
|
|
151
228
|
})
|
|
152
229
|
|
|
153
|
-
it('
|
|
230
|
+
it('opens PDF viewer when PDF thumbnail is clicked', () => {
|
|
231
|
+
renderWithProviders(
|
|
232
|
+
<MediaMessage
|
|
233
|
+
message={msg({
|
|
234
|
+
attachments: [
|
|
235
|
+
{
|
|
236
|
+
type: 'file',
|
|
237
|
+
asset_url: 'https://cdn.example.com/report.pdf',
|
|
238
|
+
mime_type: 'application/pdf',
|
|
239
|
+
title: 'Annual Report',
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
})}
|
|
243
|
+
/>
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
fireEvent.click(screen.getByRole('button', { name: 'Open PDF viewer' }))
|
|
247
|
+
expect(HTMLDialogElement.prototype.showModal).toHaveBeenCalled()
|
|
248
|
+
expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument()
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('renders unknown file as a link with no viewer button', () => {
|
|
252
|
+
renderWithProviders(
|
|
253
|
+
<MediaMessage
|
|
254
|
+
message={msg({
|
|
255
|
+
attachments: [
|
|
256
|
+
{
|
|
257
|
+
type: 'file',
|
|
258
|
+
asset_url: 'https://cdn.example.com/data.bin',
|
|
259
|
+
mime_type: 'application/octet-stream',
|
|
260
|
+
title: 'Unknown File',
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
})}
|
|
264
|
+
/>
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
// No viewer button for unknown files
|
|
268
|
+
expect(screen.queryByRole('button', { name: 'Open PDF viewer' })).not.toBeInTheDocument()
|
|
269
|
+
// Thumbnail is a plain link (opens in new tab)
|
|
270
|
+
const links = screen.getAllByRole('link')
|
|
271
|
+
expect(links.some((l) => l.getAttribute('href') === 'https://cdn.example.com/data.bin')).toBe(true)
|
|
272
|
+
expect(screen.getByText('Unknown File')).toBeInTheDocument()
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
it('shows title and file size in chin', () => {
|
|
154
276
|
renderWithProviders(
|
|
155
277
|
<MediaMessage
|
|
156
278
|
message={msg({
|
|
@@ -188,10 +310,7 @@ describe('MediaMessage', () => {
|
|
|
188
310
|
)
|
|
189
311
|
|
|
190
312
|
expect(screen.getByTestId('avatar')).toBeInTheDocument()
|
|
191
|
-
expect(screen.getByTestId('avatar')).toHaveAttribute(
|
|
192
|
-
'data-user-id',
|
|
193
|
-
'user-1'
|
|
194
|
-
)
|
|
313
|
+
expect(screen.getByTestId('avatar')).toHaveAttribute('data-user-id', 'user-1')
|
|
195
314
|
})
|
|
196
315
|
|
|
197
316
|
it('does not render Avatar for own messages', () => {
|
|
@@ -258,4 +377,144 @@ describe('MediaMessage', () => {
|
|
|
258
377
|
|
|
259
378
|
expect(container.firstChild).toBeNull()
|
|
260
379
|
})
|
|
380
|
+
|
|
381
|
+
it('renders a link card for a link attachment', () => {
|
|
382
|
+
renderWithProviders(
|
|
383
|
+
<MediaMessage
|
|
384
|
+
message={msg({
|
|
385
|
+
attachments: [
|
|
386
|
+
{
|
|
387
|
+
type: 'link',
|
|
388
|
+
og_scrape_url: 'https://linktr.ee/someone',
|
|
389
|
+
title: 'My Linktree',
|
|
390
|
+
text: 'Check out my links',
|
|
391
|
+
image_url: 'https://cdn.example.com/thumb.jpg',
|
|
392
|
+
},
|
|
393
|
+
],
|
|
394
|
+
})}
|
|
395
|
+
/>
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
const link = screen.getByRole('link')
|
|
399
|
+
expect(link).toHaveAttribute('href', 'https://linktr.ee/someone')
|
|
400
|
+
expect(screen.getByText('My Linktree')).toBeInTheDocument()
|
|
401
|
+
expect(screen.getByText('Check out my links')).toBeInTheDocument()
|
|
402
|
+
expect(screen.getByText('https://linktr.ee/someone')).toBeInTheDocument()
|
|
403
|
+
const img = screen.getByRole('img', { name: 'My Linktree' })
|
|
404
|
+
expect(img).toHaveAttribute('src', 'https://cdn.example.com/thumb.jpg')
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
it('renders a link fallback icon when link has no image', () => {
|
|
408
|
+
renderWithProviders(
|
|
409
|
+
<MediaMessage
|
|
410
|
+
message={msg({
|
|
411
|
+
attachments: [
|
|
412
|
+
{
|
|
413
|
+
type: 'link',
|
|
414
|
+
og_scrape_url: 'https://linktr.ee/someone',
|
|
415
|
+
title: 'My Linktree',
|
|
416
|
+
},
|
|
417
|
+
],
|
|
418
|
+
})}
|
|
419
|
+
/>
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
expect(screen.queryByRole('img')).not.toBeInTheDocument()
|
|
423
|
+
expect(screen.getByText('My Linktree')).toBeInTheDocument()
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
it('uses dark card background for sent messages', () => {
|
|
427
|
+
const { container } = renderWithProviders(
|
|
428
|
+
<MediaMessage
|
|
429
|
+
isMyMessage={true}
|
|
430
|
+
message={msg({
|
|
431
|
+
attachments: [
|
|
432
|
+
{
|
|
433
|
+
type: 'image',
|
|
434
|
+
image_url: 'https://cdn.example.com/photo.jpg',
|
|
435
|
+
mime_type: 'image/jpeg',
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
})}
|
|
439
|
+
/>
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
expect(container.querySelector('.bg-\\[\\#121110\\]')).toBeInTheDocument()
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
it('uses light card background for received messages', () => {
|
|
446
|
+
const { container } = renderWithProviders(
|
|
447
|
+
<MediaMessage
|
|
448
|
+
isMyMessage={false}
|
|
449
|
+
message={msg({
|
|
450
|
+
attachments: [
|
|
451
|
+
{
|
|
452
|
+
type: 'image',
|
|
453
|
+
image_url: 'https://cdn.example.com/photo.jpg',
|
|
454
|
+
mime_type: 'image/jpeg',
|
|
455
|
+
},
|
|
456
|
+
],
|
|
457
|
+
})}
|
|
458
|
+
/>
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
expect(container.querySelector('.bg-\\[\\#F3F3F1\\]')).toBeInTheDocument()
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
it('shows download button for image attachment', () => {
|
|
465
|
+
renderWithProviders(
|
|
466
|
+
<MediaMessage
|
|
467
|
+
message={msg({
|
|
468
|
+
attachments: [
|
|
469
|
+
{
|
|
470
|
+
type: 'image',
|
|
471
|
+
image_url: 'https://cdn.example.com/photo.jpg',
|
|
472
|
+
mime_type: 'image/jpeg',
|
|
473
|
+
title: 'My Photo',
|
|
474
|
+
},
|
|
475
|
+
],
|
|
476
|
+
})}
|
|
477
|
+
/>
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
expect(screen.getByRole('button', { name: 'Download' })).toBeInTheDocument()
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
it('shows download button for audio attachment', () => {
|
|
484
|
+
renderWithProviders(
|
|
485
|
+
<MediaMessage
|
|
486
|
+
message={msg({
|
|
487
|
+
attachments: [
|
|
488
|
+
{
|
|
489
|
+
type: 'audio',
|
|
490
|
+
asset_url: 'https://cdn.example.com/track.mp3',
|
|
491
|
+
mime_type: 'audio/mpeg',
|
|
492
|
+
title: 'My Track',
|
|
493
|
+
},
|
|
494
|
+
],
|
|
495
|
+
})}
|
|
496
|
+
/>
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
expect(screen.getByRole('button', { name: 'Download' })).toBeInTheDocument()
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
it('does not show download or expand buttons for link cards', () => {
|
|
503
|
+
renderWithProviders(
|
|
504
|
+
<MediaMessage
|
|
505
|
+
message={msg({
|
|
506
|
+
attachments: [
|
|
507
|
+
{
|
|
508
|
+
type: 'link',
|
|
509
|
+
og_scrape_url: 'https://linktr.ee/someone',
|
|
510
|
+
title: 'My Linktree',
|
|
511
|
+
},
|
|
512
|
+
],
|
|
513
|
+
})}
|
|
514
|
+
/>
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
expect(screen.queryByRole('button', { name: 'Download' })).not.toBeInTheDocument()
|
|
518
|
+
expect(screen.queryByRole('button', { name: 'View full screen' })).not.toBeInTheDocument()
|
|
519
|
+
})
|
|
261
520
|
})
|