@linktr.ee/messaging-react 4.2.1 → 4.3.0-rc-1787410254

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.
@@ -1,494 +0,0 @@
1
- import React from 'react'
2
- import type { LocalMessage } from 'stream-chat'
3
- import { describe, expect, it, vi } from 'vitest'
4
-
5
- import { renderWithProviders, screen, fireEvent } from '../../test/utils'
6
-
7
- import { MediaMessage, resolveLinkAttachment } from '.'
8
-
9
- vi.mock('../Avatar', () => ({
10
- Avatar: ({ id }: { id: string }) => (
11
- <div data-testid="avatar" data-user-id={id} />
12
- ),
13
- }))
14
-
15
- vi.mock('../AttachmentCard/MediaPlayer', () => ({
16
- default: ({ source, mimeType }: { source: string; mimeType: string }) => (
17
- <div
18
- role="button"
19
- tabIndex={0}
20
- data-testid="media-player"
21
- data-source={source}
22
- data-mime-type={mimeType}
23
- />
24
- ),
25
- }))
26
-
27
- vi.mock('../AttachmentCard/utils/icons', () => ({
28
- renderTypeIcon: () => <span data-testid="type-icon" />,
29
- }))
30
-
31
- vi.mock('../AttachmentCard/utils/mimeType', () => ({
32
- getSourceType: (mimeType: string) => {
33
- if (mimeType.startsWith('video/')) return 'video'
34
- if (mimeType.startsWith('image/')) return 'image'
35
- if (mimeType.startsWith('audio/')) return 'audio'
36
- return 'document'
37
- },
38
- }))
39
-
40
- const msg = (overrides: Record<string, unknown> = {}): LocalMessage =>
41
- ({
42
- id: 'msg-1',
43
- text: '',
44
- type: 'regular',
45
- created_at: new Date(),
46
- updated_at: new Date(),
47
- deleted_at: null,
48
- pinned_at: null,
49
- status: 'received',
50
- user: { id: 'user-1', name: 'Alice' },
51
- attachments: [],
52
- ...overrides,
53
- }) as unknown as LocalMessage
54
-
55
- describe('MediaMessage', () => {
56
- it('renders nothing when no media URL is resolvable', () => {
57
- const { container } = renderWithProviders(<MediaMessage message={msg()} />)
58
- expect(container.firstChild).toBeNull()
59
- })
60
-
61
- it('renders MediaPlayer immediately for a video attachment', () => {
62
- renderWithProviders(
63
- <MediaMessage
64
- message={msg({
65
- attachments: [
66
- {
67
- type: 'video',
68
- asset_url: 'https://cdn.example.com/clip.mp4',
69
- mime_type: 'video/mp4',
70
- title: 'My Clip',
71
- },
72
- ],
73
- })}
74
- />
75
- )
76
-
77
- const player = screen.getByTestId('media-player')
78
- expect(player).toBeInTheDocument()
79
- expect(player).toHaveAttribute(
80
- 'data-source',
81
- 'https://cdn.example.com/clip.mp4'
82
- )
83
- })
84
-
85
- it('renders MediaPlayer for an audio attachment', () => {
86
- renderWithProviders(
87
- <MediaMessage
88
- message={msg({
89
- attachments: [
90
- {
91
- type: 'audio',
92
- asset_url: 'https://cdn.example.com/track.mp3',
93
- mime_type: 'audio/mpeg',
94
- },
95
- ],
96
- })}
97
- />
98
- )
99
-
100
- expect(screen.getByTestId('media-player')).toHaveAttribute(
101
- 'data-source',
102
- 'https://cdn.example.com/track.mp3'
103
- )
104
- })
105
-
106
- it('renders an img for an image attachment', () => {
107
- renderWithProviders(
108
- <MediaMessage
109
- message={msg({
110
- attachments: [
111
- {
112
- type: 'image',
113
- image_url: 'https://cdn.example.com/photo.jpg',
114
- mime_type: 'image/jpeg',
115
- title: 'My Photo',
116
- },
117
- ],
118
- })}
119
- />
120
- )
121
-
122
- const image = screen.getByRole('img', { name: 'My Photo' })
123
- expect(image).toHaveAttribute('src', 'https://cdn.example.com/photo.jpg')
124
- })
125
-
126
- it('renders unknown file type as icon placeholder, not a link', () => {
127
- renderWithProviders(
128
- <MediaMessage
129
- message={msg({
130
- attachments: [
131
- {
132
- type: 'file',
133
- asset_url: 'https://cdn.example.com/data.bin',
134
- mime_type: 'application/octet-stream',
135
- title: 'Unknown File',
136
- },
137
- ],
138
- })}
139
- />
140
- )
141
-
142
- expect(screen.queryByRole('link')).not.toBeInTheDocument()
143
- expect(screen.getByText('Unknown File')).toBeInTheDocument()
144
- expect(screen.getAllByTestId('type-icon').length).toBeGreaterThanOrEqual(1)
145
- })
146
-
147
- it('shows title and file size in meta row', () => {
148
- renderWithProviders(
149
- <MediaMessage
150
- message={msg({
151
- attachments: [
152
- {
153
- type: 'video',
154
- asset_url: 'https://cdn.example.com/clip.mp4',
155
- mime_type: 'video/mp4',
156
- title: 'Clip',
157
- file_size: 2048,
158
- },
159
- ],
160
- })}
161
- />
162
- )
163
-
164
- expect(screen.getByText('Clip')).toBeInTheDocument()
165
- expect(screen.getByText('2.0 KB')).toBeInTheDocument()
166
- })
167
-
168
- it('renders Avatar for a message from another user', () => {
169
- renderWithProviders(
170
- <MediaMessage
171
- isMyMessage={false}
172
- message={msg({
173
- attachments: [
174
- {
175
- type: 'image',
176
- image_url: 'https://cdn.example.com/photo.jpg',
177
- mime_type: 'image/jpeg',
178
- },
179
- ],
180
- })}
181
- />
182
- )
183
-
184
- expect(screen.getByTestId('avatar')).toBeInTheDocument()
185
- expect(screen.getByTestId('avatar')).toHaveAttribute(
186
- 'data-user-id',
187
- 'user-1'
188
- )
189
- })
190
-
191
- it('does not render Avatar for own messages', () => {
192
- renderWithProviders(
193
- <MediaMessage
194
- isMyMessage={true}
195
- message={msg({
196
- attachments: [
197
- {
198
- type: 'image',
199
- image_url: 'https://cdn.example.com/photo.jpg',
200
- mime_type: 'image/jpeg',
201
- },
202
- ],
203
- })}
204
- />
205
- )
206
-
207
- expect(screen.queryByTestId('avatar')).not.toBeInTheDocument()
208
- })
209
-
210
- it('applies the --me class for own messages', () => {
211
- const { container } = renderWithProviders(
212
- <MediaMessage
213
- isMyMessage={true}
214
- message={msg({
215
- attachments: [
216
- {
217
- type: 'image',
218
- image_url: 'https://cdn.example.com/photo.jpg',
219
- mime_type: 'image/jpeg',
220
- },
221
- ],
222
- })}
223
- />
224
- )
225
-
226
- expect(container.firstChild).toHaveClass('str-chat__message--me')
227
- })
228
-
229
- it('applies the --other class for messages from other users', () => {
230
- const { container } = renderWithProviders(
231
- <MediaMessage
232
- isMyMessage={false}
233
- message={msg({
234
- attachments: [
235
- {
236
- type: 'image',
237
- image_url: 'https://cdn.example.com/photo.jpg',
238
- mime_type: 'image/jpeg',
239
- },
240
- ],
241
- })}
242
- />
243
- )
244
-
245
- expect(container.firstChild).toHaveClass('str-chat__message--other')
246
- })
247
-
248
- it('renders nothing when no attachments', () => {
249
- const { container } = renderWithProviders(
250
- <MediaMessage message={msg({ attachments: [] })} />
251
- )
252
-
253
- expect(container.firstChild).toBeNull()
254
- })
255
-
256
- it('shows Download action for received (Visitor) image attachment', () => {
257
- renderWithProviders(
258
- <MediaMessage
259
- message={msg({
260
- attachments: [
261
- {
262
- type: 'image',
263
- image_url: 'https://cdn.example.com/photo.jpg',
264
- mime_type: 'image/jpeg',
265
- title: 'My Photo',
266
- },
267
- ],
268
- })}
269
- />
270
- )
271
-
272
- expect(screen.getByRole('button', { name: 'Download' })).toBeInTheDocument()
273
- })
274
-
275
- it('does not show literal placeholder title for sent image without title', () => {
276
- renderWithProviders(
277
- <MediaMessage
278
- isMyMessage={true}
279
- message={msg({
280
- attachments: [
281
- {
282
- type: 'image',
283
- image_url: 'https://cdn.example.com/photo.jpg',
284
- mime_type: 'image/jpeg',
285
- },
286
- ],
287
- })}
288
- />
289
- )
290
-
291
- expect(screen.queryByText('Attachment title')).not.toBeInTheDocument()
292
- })
293
-
294
- it('does not show Download button for sent (Creator) image attachment', () => {
295
- renderWithProviders(
296
- <MediaMessage
297
- isMyMessage={true}
298
- message={msg({
299
- attachments: [
300
- {
301
- type: 'image',
302
- image_url: 'https://cdn.example.com/photo.jpg',
303
- mime_type: 'image/jpeg',
304
- title: 'My Photo',
305
- },
306
- ],
307
- })}
308
- />
309
- )
310
-
311
- expect(
312
- screen.queryByRole('button', { name: 'Download' })
313
- ).not.toBeInTheDocument()
314
- })
315
-
316
- it('shows Download action for received audio attachment', () => {
317
- renderWithProviders(
318
- <MediaMessage
319
- message={msg({
320
- attachments: [
321
- {
322
- type: 'audio',
323
- asset_url: 'https://cdn.example.com/track.mp3',
324
- mime_type: 'audio/mpeg',
325
- title: 'My Track',
326
- },
327
- ],
328
- })}
329
- />
330
- )
331
-
332
- expect(screen.getByRole('button', { name: 'Download' })).toBeInTheDocument()
333
- })
334
-
335
- it('renders a link card for a link attachment', () => {
336
- renderWithProviders(
337
- <MediaMessage
338
- message={msg({
339
- attachments: [
340
- {
341
- type: 'link',
342
- og_scrape_url: 'https://linktr.ee/someone',
343
- title: 'My Linktree',
344
- text: 'Check out my links',
345
- image_url: 'https://cdn.example.com/thumb.jpg',
346
- },
347
- ],
348
- })}
349
- />
350
- )
351
-
352
- const link = screen.getByRole('link')
353
- expect(link).toHaveAttribute('href', 'https://linktr.ee/someone')
354
- expect(screen.getByText('My Linktree')).toBeInTheDocument()
355
- expect(screen.getByText('Check out my links')).toBeInTheDocument()
356
- expect(screen.getByText('https://linktr.ee/someone')).toBeInTheDocument()
357
- const img = screen.getByRole('img', { name: 'My Linktree' })
358
- expect(img).toHaveAttribute('src', 'https://cdn.example.com/thumb.jpg')
359
- })
360
-
361
- it('renders a link fallback icon when link has no image', () => {
362
- renderWithProviders(
363
- <MediaMessage
364
- message={msg({
365
- attachments: [
366
- {
367
- type: 'link',
368
- og_scrape_url: 'https://linktr.ee/someone',
369
- title: 'My Linktree',
370
- },
371
- ],
372
- })}
373
- />
374
- )
375
-
376
- expect(screen.queryByRole('img')).not.toBeInTheDocument()
377
- expect(screen.getByText('My Linktree')).toBeInTheDocument()
378
- })
379
-
380
- it('does not show download or expand buttons for link cards', () => {
381
- renderWithProviders(
382
- <MediaMessage
383
- message={msg({
384
- attachments: [
385
- {
386
- type: 'link',
387
- og_scrape_url: 'https://linktr.ee/someone',
388
- title: 'My Linktree',
389
- },
390
- ],
391
- })}
392
- />
393
- )
394
-
395
- expect(
396
- screen.queryByRole('button', { name: 'Download' })
397
- ).not.toBeInTheDocument()
398
- expect(
399
- screen.queryByRole('button', { name: 'View full screen' })
400
- ).not.toBeInTheDocument()
401
- })
402
-
403
- it('MediaMessage.Visitor renders card without chat shell', () => {
404
- const { container } = renderWithProviders(
405
- <MediaMessage.Visitor
406
- message={msg({
407
- attachments: [
408
- {
409
- type: 'image',
410
- image_url: 'https://cdn.example.com/photo.jpg',
411
- mime_type: 'image/jpeg',
412
- title: 'Solo',
413
- },
414
- ],
415
- })}
416
- />
417
- )
418
-
419
- expect(
420
- container.querySelector('.str-chat__message')
421
- ).not.toBeInTheDocument()
422
- expect(screen.getByRole('button', { name: 'Download' })).toBeInTheDocument()
423
- })
424
-
425
- it('MediaMessage.Creator renders card without chat shell', () => {
426
- const { container } = renderWithProviders(
427
- <MediaMessage.Creator
428
- message={msg({
429
- attachments: [
430
- {
431
- type: 'image',
432
- image_url: 'https://cdn.example.com/photo.jpg',
433
- mime_type: 'image/jpeg',
434
- },
435
- ],
436
- })}
437
- />
438
- )
439
-
440
- expect(
441
- container.querySelector('.str-chat__message')
442
- ).not.toBeInTheDocument()
443
- expect(
444
- screen.queryByRole('button', { name: 'Download' })
445
- ).not.toBeInTheDocument()
446
- })
447
-
448
- it('resolveLinkAttachment ignores empty-string og_scrape_url without asset_url', () => {
449
- const message = msg({
450
- attachments: [
451
- {
452
- type: 'image',
453
- image_url: 'https://cdn.example.com/photo.jpg',
454
- mime_type: 'image/jpeg',
455
- og_scrape_url: '',
456
- },
457
- ],
458
- })
459
- expect(resolveLinkAttachment(message)).toBeUndefined()
460
- })
461
-
462
- it('Download button triggers fetch and uses fallback on failure', async () => {
463
- const fetchSpy = vi
464
- .spyOn(global, 'fetch')
465
- .mockRejectedValue(new Error('fail'))
466
- const openSpy = vi.spyOn(window, 'open').mockReturnValue({
467
- location: { href: '' },
468
- close: vi.fn(),
469
- } as unknown as Window)
470
-
471
- renderWithProviders(
472
- <MediaMessage
473
- message={msg({
474
- attachments: [
475
- {
476
- type: 'image',
477
- image_url: 'https://cdn.example.com/photo.jpg',
478
- mime_type: 'image/jpeg',
479
- title: 'My Photo',
480
- },
481
- ],
482
- })}
483
- />
484
- )
485
-
486
- fireEvent.click(screen.getByRole('button', { name: 'Download' }))
487
- await vi.waitFor(() => {
488
- expect(fetchSpy).toHaveBeenCalled()
489
- })
490
-
491
- fetchSpy.mockRestore()
492
- openSpy.mockRestore()
493
- })
494
- })