@linktr.ee/messaging-react 4.4.7 → 4.5.0-rc-1788297811
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/assets/index.css +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +58 -43
- package/dist/index.js +1167 -1033
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/AttachmentCard/MediaPlayer.test.tsx +9 -5
- package/src/components/AttachmentCard/Thumbnail.test.tsx +4 -0
- package/src/components/ChannelActionsMenu/ChannelActionsMenu.test.tsx +18 -10
- package/src/components/ChannelList/ChannelListContext.test.tsx +7 -5
- package/src/components/ChannelList/CustomChannelPreview.test.tsx +7 -5
- package/src/components/ChannelList/index.test.tsx +1 -0
- package/src/components/ChannelView.test.tsx +20 -13
- package/src/components/CustomLinkPreviewList/CustomLinkPreviewList.test.tsx +4 -3
- package/src/components/CustomMessage/CustomMessage.test.tsx +6 -5
- package/src/components/CustomMessage/MessageTag.test.tsx +6 -0
- package/src/components/CustomMessage/SentMessageDeliveryStatus.test.tsx +19 -9
- package/src/components/CustomMessage/StreamAttachmentMessage.test.tsx +45 -5
- package/src/components/CustomMessage/StreamAttachmentMessage.tsx +6 -0
- package/src/components/CustomMessage/TipMessage/TipMessage.test.tsx +3 -0
- package/src/components/CustomMessageInput/CustomMessageInput.test.tsx +16 -7
- package/src/components/FaqList/FaqList.test.tsx +20 -12
- package/src/components/LinkAttachment/LinkAttachment.test.tsx +27 -14
- package/src/components/LinkAttachment/components/_shared/BubbleTail.test.tsx +1 -0
- package/src/components/LinkAttachment/components/_shared/CardBody.test.tsx +6 -6
- package/src/components/LinkAttachment/components/_shared/CardCta.test.tsx +8 -5
- package/src/components/LinkAttachment/components/_shared/CardShell.test.tsx +7 -5
- package/src/components/LinkAttachment/components/_shared/CardThumbnail.test.tsx +3 -8
- package/src/components/MessageAttachment/Audio/AudioAttachment.stories.tsx +165 -58
- package/src/components/MessageAttachment/Audio/WaveformAudioRow.test.tsx +155 -0
- package/src/components/MessageAttachment/Audio/WaveformAudioRow.tsx +455 -0
- package/src/components/MessageAttachment/Audio/audioRowRequester.test.ts +24 -0
- package/src/components/MessageAttachment/Audio/audioRowRequester.ts +16 -0
- package/src/components/MessageAttachment/Audio/index.tsx +83 -79
- package/src/components/MessageAttachment/MessageAttachment.behavior.test.tsx +22 -15
- package/src/components/MessageAttachment/MessageAttachment.test.tsx +279 -134
- package/src/components/MessageAttachment/_shared/Bubble.test.tsx +2 -0
- package/src/components/MessageAttachment/_shared/CarouselNav.test.tsx +5 -4
- package/src/components/MessageAttachment/_shared/CompactDocumentRow.test.tsx +4 -3
- package/src/components/MessageAttachment/_shared/DismissButton.test.tsx +4 -3
- package/src/components/MessageAttachment/_shared/DownloadAction.test.tsx +8 -5
- package/src/components/MessageAttachment/_shared/ImageViewer.test.tsx +6 -3
- package/src/components/MessageAttachment/_shared/MediaStackGrid.test.tsx +11 -4
- package/src/components/MessageAttachment/_shared/PdfViewer.test.tsx +5 -3
- package/src/components/MessageAttachment/_shared/VideoViewer.test.tsx +12 -3
- package/src/components/MessageAttachment/_shared/ViewerShell.test.tsx +13 -6
- package/src/components/MessageAttachment/_shared/useCarousel.test.tsx +10 -4
- package/src/components/MessageAttachment/_shared/useIsClient.ts +23 -0
- package/src/components/MessageAttachment/index.tsx +25 -9
- package/src/components/MessageAttachment/types.ts +19 -9
- package/src/components/MessageBubble/MessageBubble.test.tsx +8 -0
- package/src/components/RichCard/RichCard.test.tsx +10 -2
- package/src/styles.css +120 -0
|
@@ -3,10 +3,10 @@ import type { Attachment, LocalMessage } from 'stream-chat'
|
|
|
3
3
|
import { describe, expect, it } from 'vitest'
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
-
fireEvent,
|
|
7
6
|
renderWithProviders,
|
|
8
7
|
screen,
|
|
9
8
|
within,
|
|
9
|
+
userEvent,
|
|
10
10
|
} from '../../test/utils'
|
|
11
11
|
|
|
12
12
|
import StreamAttachmentMessage, {
|
|
@@ -198,11 +198,17 @@ describe('StreamAttachmentMessage', () => {
|
|
|
198
198
|
)
|
|
199
199
|
|
|
200
200
|
const link = screen.getByRole('link', { name: /my linktree/i })
|
|
201
|
+
const linkCard = screen.getByTestId('link-card')
|
|
201
202
|
const imageBubble = screen.getByTestId('image-attachment')
|
|
202
203
|
|
|
203
|
-
|
|
204
|
-
|
|
204
|
+
const attachmentRows = within(screen.getByTestId('message-attachment-rows'))
|
|
205
|
+
const attachments = attachmentRows.getAllByTestId(
|
|
206
|
+
/link-card|image-attachment/
|
|
205
207
|
)
|
|
208
|
+
expect(attachments.indexOf(linkCard)).toBeLessThan(
|
|
209
|
+
attachments.indexOf(imageBubble)
|
|
210
|
+
)
|
|
211
|
+
expect(link).toBeInTheDocument()
|
|
206
212
|
expect(screen.getByTestId('message-caption-bubble')).toHaveTextContent(
|
|
207
213
|
'caption'
|
|
208
214
|
)
|
|
@@ -257,7 +263,8 @@ describe('StreamAttachmentMessage', () => {
|
|
|
257
263
|
)
|
|
258
264
|
})
|
|
259
265
|
|
|
260
|
-
it('keeps the original image source in the viewer for channel image attachments', () => {
|
|
266
|
+
it('keeps the original image source in the viewer for channel image attachments', async () => {
|
|
267
|
+
const user = userEvent.setup()
|
|
261
268
|
renderWithProviders(
|
|
262
269
|
<StreamAttachmentMessage
|
|
263
270
|
groupPosition="single"
|
|
@@ -272,7 +279,7 @@ describe('StreamAttachmentMessage', () => {
|
|
|
272
279
|
/>
|
|
273
280
|
)
|
|
274
281
|
|
|
275
|
-
|
|
282
|
+
await user.click(screen.getByLabelText('Open image 1 of 1'))
|
|
276
283
|
const viewer = screen.getByTestId('image-viewer')
|
|
277
284
|
|
|
278
285
|
expect(viewer).toHaveAttribute('open')
|
|
@@ -361,4 +368,37 @@ describe('StreamAttachmentMessage', () => {
|
|
|
361
368
|
'from the trip'
|
|
362
369
|
)
|
|
363
370
|
})
|
|
371
|
+
|
|
372
|
+
it('forwards a persisted duration and waveform to the audio card', () => {
|
|
373
|
+
// Audio is sent as `type: "file"` with an `audio/*` mime type (for
|
|
374
|
+
// mobile compatibility), and the composer persists `duration` +
|
|
375
|
+
// `waveform_data` on the outgoing attachment. Without this
|
|
376
|
+
// passthrough every sent and received card would fall back to
|
|
377
|
+
// `--:--` and a flat placeholder even though the data is there.
|
|
378
|
+
renderWithProviders(
|
|
379
|
+
<StreamAttachmentMessage
|
|
380
|
+
groupPosition="single"
|
|
381
|
+
isMyMessage
|
|
382
|
+
message={message([
|
|
383
|
+
{
|
|
384
|
+
type: 'file',
|
|
385
|
+
asset_url: 'https://cdn.example.com/take-1.mp3',
|
|
386
|
+
mime_type: 'audio/mpeg',
|
|
387
|
+
title: 'take-1.mp3',
|
|
388
|
+
duration: 12,
|
|
389
|
+
waveform_data: [0.2, 0.9, 0.5, 0.7],
|
|
390
|
+
},
|
|
391
|
+
])}
|
|
392
|
+
/>
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
expect(screen.getByText('00:12')).toBeInTheDocument()
|
|
396
|
+
// The flat placeholder renders every bar at 0%; a real waveform is
|
|
397
|
+
// observable as bars drawn above that floor.
|
|
398
|
+
const barHeights = screen
|
|
399
|
+
.getAllByTestId('amplitude-bar')
|
|
400
|
+
.map((bar) => bar.style.getPropertyValue('--mes-audio-wave-bar'))
|
|
401
|
+
expect(barHeights).toHaveLength(35)
|
|
402
|
+
expect(barHeights.some((height) => height !== '0%')).toBe(true)
|
|
403
|
+
})
|
|
364
404
|
})
|
|
@@ -255,6 +255,12 @@ function mediaItemForKind(
|
|
|
255
255
|
src: trimToUndefined(attachment.asset_url) ?? '',
|
|
256
256
|
mimeType: trimToUndefined(attachment.mime_type) ?? 'audio/mpeg',
|
|
257
257
|
filename: trimToUndefined((attachment as { title?: string }).title),
|
|
258
|
+
// Both are first-class `stream-chat` Attachment fields. The
|
|
259
|
+
// composer writes them at send time so the waveform card paints
|
|
260
|
+
// the real amplitudes and the correct duration with no remote
|
|
261
|
+
// decode and no CDN CORS exposure.
|
|
262
|
+
durationSeconds: attachment.duration,
|
|
263
|
+
waveformData: attachment.waveform_data,
|
|
258
264
|
}
|
|
259
265
|
case 'pdf':
|
|
260
266
|
return {
|
|
@@ -34,6 +34,7 @@ describe('TipMessage', () => {
|
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
const tipTag = screen.getByText('$5.50 tip!')
|
|
37
|
+
// eslint-disable-next-line testing-library/no-node-access -- the component wrapper class has no semantic query.
|
|
37
38
|
expect(tipTag.closest('.message-tip-pill')).not.toBeNull()
|
|
38
39
|
})
|
|
39
40
|
|
|
@@ -48,6 +49,7 @@ describe('TipMessage', () => {
|
|
|
48
49
|
)
|
|
49
50
|
|
|
50
51
|
const tipTag = screen.getByText('$50.00 tip!')
|
|
52
|
+
// eslint-disable-next-line testing-library/no-node-access -- the component wrapper class has no semantic query.
|
|
51
53
|
expect(tipTag.closest('.message-tip-pill')).not.toBeNull()
|
|
52
54
|
})
|
|
53
55
|
|
|
@@ -60,6 +62,7 @@ describe('TipMessage', () => {
|
|
|
60
62
|
/>
|
|
61
63
|
)
|
|
62
64
|
|
|
65
|
+
// eslint-disable-next-line testing-library/no-node-access -- the null-rendering DOM contract has no semantic query.
|
|
63
66
|
expect(container.firstChild).toBeNull()
|
|
64
67
|
})
|
|
65
68
|
})
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
userEvent,
|
|
6
|
+
fireEvent,
|
|
7
|
+
renderWithProviders,
|
|
8
|
+
screen,
|
|
9
|
+
within,
|
|
10
|
+
} from '../../test/utils'
|
|
5
11
|
|
|
6
12
|
import { CustomMessageInput } from '.'
|
|
7
13
|
|
|
@@ -207,7 +213,8 @@ describe('CustomMessageInput', () => {
|
|
|
207
213
|
expect(notCancelled).toBe(false)
|
|
208
214
|
})
|
|
209
215
|
|
|
210
|
-
it('still submits on click', () => {
|
|
216
|
+
it('still submits on click', async () => {
|
|
217
|
+
const user = userEvent.setup()
|
|
211
218
|
mockChannelData = {}
|
|
212
219
|
mockContextSendButton = undefined
|
|
213
220
|
mockHasSendableData = true
|
|
@@ -215,7 +222,7 @@ describe('CustomMessageInput', () => {
|
|
|
215
222
|
renderWithProviders(<CustomMessageInput />)
|
|
216
223
|
|
|
217
224
|
const sendButton = screen.getByTestId('send-button')
|
|
218
|
-
|
|
225
|
+
await user.click(sendButton)
|
|
219
226
|
expect(mockHandleSubmit).toHaveBeenCalledTimes(1)
|
|
220
227
|
})
|
|
221
228
|
})
|
|
@@ -285,10 +292,12 @@ describe('CustomMessageInput', () => {
|
|
|
285
292
|
const messageInput = screen.getByTestId('message-input')
|
|
286
293
|
|
|
287
294
|
expect(screen.getByTestId('composer-chrome')).toContainElement(header)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
).
|
|
295
|
+
const chromeChildren = within(
|
|
296
|
+
screen.getByTestId('composer-chrome')
|
|
297
|
+
).getAllByTestId(/composer-header|message-input/)
|
|
298
|
+
expect(chromeChildren.indexOf(header)).toBeLessThan(
|
|
299
|
+
chromeChildren.indexOf(messageInput)
|
|
300
|
+
)
|
|
292
301
|
})
|
|
293
302
|
|
|
294
303
|
it('keeps the frozen wrapper around a custom composerInput', () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { describe, expect, it, vi } from 'vitest'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { userEvent, renderWithProviders, screen } from '../../test/utils'
|
|
5
5
|
import { getAvatarEmoji } from '../Avatar/getAvatarEmoji'
|
|
6
6
|
|
|
7
7
|
import { FaqListItem } from './FaqListItem'
|
|
@@ -70,7 +70,8 @@ describe('FaqListItem', () => {
|
|
|
70
70
|
).toHaveStyle({ backgroundColor: '#E6E5E3' })
|
|
71
71
|
})
|
|
72
72
|
|
|
73
|
-
it('uses loading-only classes and blocks clicks while loading', () => {
|
|
73
|
+
it('uses loading-only classes and blocks clicks while loading', async () => {
|
|
74
|
+
const user = userEvent.setup()
|
|
74
75
|
const onClick = vi.fn()
|
|
75
76
|
renderWithProviders(
|
|
76
77
|
<FaqListItem question="How do I get started?" onClick={onClick} loading />
|
|
@@ -86,17 +87,18 @@ describe('FaqListItem', () => {
|
|
|
86
87
|
'cursor-not-allowed',
|
|
87
88
|
])
|
|
88
89
|
|
|
89
|
-
|
|
90
|
+
await user.click(button)
|
|
90
91
|
expect(onClick).not.toHaveBeenCalled()
|
|
91
92
|
})
|
|
92
93
|
|
|
93
|
-
it('calls the click handler once when enabled', () => {
|
|
94
|
+
it('calls the click handler once when enabled', async () => {
|
|
95
|
+
const user = userEvent.setup()
|
|
94
96
|
const onClick = vi.fn()
|
|
95
97
|
renderWithProviders(
|
|
96
98
|
<FaqListItem question="How do I get started?" onClick={onClick} />
|
|
97
99
|
)
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
await user.click(screen.getByRole('button'))
|
|
100
102
|
expect(onClick).toHaveBeenCalledTimes(1)
|
|
101
103
|
})
|
|
102
104
|
})
|
|
@@ -110,16 +112,20 @@ describe('FaqList', () => {
|
|
|
110
112
|
/>
|
|
111
113
|
)
|
|
112
114
|
|
|
115
|
+
// eslint-disable-next-line testing-library/no-node-access -- the null-rendering DOM contract has no semantic query.
|
|
113
116
|
expect(container.firstChild).toBeNull()
|
|
114
117
|
})
|
|
115
118
|
|
|
116
119
|
it('renders enabled FAQs in ascending order and filters disabled FAQs', () => {
|
|
117
120
|
renderWithProviders(<FaqList faqs={faqs} onFaqClick={vi.fn()} />)
|
|
118
121
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
const firstFaq = screen.getByRole('button', {
|
|
123
|
+
name: 'How do I get started?',
|
|
124
|
+
})
|
|
125
|
+
// eslint-disable-next-line testing-library/no-node-access -- the wrapper class is a deliberate DOM contract with no semantic query.
|
|
126
|
+
expect(firstFaq.parentElement).toHaveStyle({
|
|
127
|
+
backgroundColor: '#F1F0EE',
|
|
128
|
+
})
|
|
123
129
|
expect(
|
|
124
130
|
screen.getAllByRole('button').map((button) => button.textContent)
|
|
125
131
|
).toEqual(['How do I get started?', 'What payment methods do you accept?'])
|
|
@@ -155,6 +161,7 @@ describe('FaqList', () => {
|
|
|
155
161
|
/>
|
|
156
162
|
)
|
|
157
163
|
|
|
164
|
+
// eslint-disable-next-line testing-library/no-node-access -- the component shell wrapper has no semantic query.
|
|
158
165
|
expect(container.firstElementChild).toHaveClass('faq-list-custom-class')
|
|
159
166
|
})
|
|
160
167
|
|
|
@@ -180,7 +187,8 @@ describe('FaqList', () => {
|
|
|
180
187
|
expect(screen.queryAllByTestId('avatar-ring')).toHaveLength(1)
|
|
181
188
|
})
|
|
182
189
|
|
|
183
|
-
it('passes the clicked FAQ id and marks only its matching row as loading', () => {
|
|
190
|
+
it('passes the clicked FAQ id and marks only its matching row as loading', async () => {
|
|
191
|
+
const user = userEvent.setup()
|
|
184
192
|
const onFaqClick = vi.fn()
|
|
185
193
|
renderWithProviders(
|
|
186
194
|
<FaqList faqs={faqs} onFaqClick={onFaqClick} loadingFaqId="second" />
|
|
@@ -197,8 +205,8 @@ describe('FaqList', () => {
|
|
|
197
205
|
expect(firstButton).not.toHaveClass('opacity-50', 'cursor-not-allowed')
|
|
198
206
|
expect(secondButton).toHaveClass('opacity-50', 'cursor-not-allowed')
|
|
199
207
|
|
|
200
|
-
|
|
201
|
-
|
|
208
|
+
await user.click(firstButton)
|
|
209
|
+
await user.click(secondButton)
|
|
202
210
|
expect(onFaqClick).toHaveBeenCalledTimes(1)
|
|
203
211
|
expect(onFaqClick).toHaveBeenCalledWith('first')
|
|
204
212
|
})
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { describe, expect, it, vi } from 'vitest'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
userEvent,
|
|
6
|
+
renderWithProviders,
|
|
7
|
+
screen,
|
|
8
|
+
fireEvent,
|
|
9
|
+
} from '../../test/utils'
|
|
5
10
|
|
|
6
11
|
import { AUDIO_BG_CLASS } from './components/_shared/CardThumbnail'
|
|
7
12
|
|
|
@@ -28,6 +33,7 @@ describe('LinkAttachment.Received (link previews)', () => {
|
|
|
28
33
|
})
|
|
29
34
|
|
|
30
35
|
it('renders as a button when no URL is set and onClick is provided', async () => {
|
|
36
|
+
const user = userEvent.setup()
|
|
31
37
|
const handleClick = vi.fn()
|
|
32
38
|
renderWithProviders(
|
|
33
39
|
<LinkAttachment.Received
|
|
@@ -38,11 +44,12 @@ describe('LinkAttachment.Received (link previews)', () => {
|
|
|
38
44
|
)
|
|
39
45
|
const root = await screen.findByTestId('link-attachment')
|
|
40
46
|
expect(root.tagName).toBe('BUTTON')
|
|
41
|
-
|
|
47
|
+
await user.click(root)
|
|
42
48
|
expect(handleClick).toHaveBeenCalledTimes(1)
|
|
43
49
|
})
|
|
44
50
|
|
|
45
51
|
it('routes the card-level onClick through the CTA when one is set', async () => {
|
|
52
|
+
const user = userEvent.setup()
|
|
46
53
|
const onClick = vi.fn()
|
|
47
54
|
const ctaOnClick = vi.fn()
|
|
48
55
|
renderWithProviders(
|
|
@@ -53,7 +60,7 @@ describe('LinkAttachment.Received (link previews)', () => {
|
|
|
53
60
|
onClick={onClick}
|
|
54
61
|
/>
|
|
55
62
|
)
|
|
56
|
-
|
|
63
|
+
await user.click(await screen.findByText('View FAQs'))
|
|
57
64
|
expect(onClick).toHaveBeenCalledTimes(1)
|
|
58
65
|
expect(ctaOnClick).toHaveBeenCalledTimes(1)
|
|
59
66
|
})
|
|
@@ -93,6 +100,7 @@ describe('LinkAttachment.Received (link previews)', () => {
|
|
|
93
100
|
|
|
94
101
|
describe('LinkAttachment.Sent', () => {
|
|
95
102
|
it('renders the dark variant as an external link when url is set', async () => {
|
|
103
|
+
const user = userEvent.setup()
|
|
96
104
|
const handleClick = vi.fn()
|
|
97
105
|
renderWithProviders(
|
|
98
106
|
<LinkAttachment.Sent
|
|
@@ -107,7 +115,7 @@ describe('LinkAttachment.Sent', () => {
|
|
|
107
115
|
expect(link.getAttribute('href')).toBe('https://tr.ee/briemix')
|
|
108
116
|
expect(link.getAttribute('target')).toBe('_blank')
|
|
109
117
|
expect(link.getAttribute('rel')).toBe('noopener noreferrer')
|
|
110
|
-
|
|
118
|
+
await user.click(link)
|
|
111
119
|
expect(handleClick).toHaveBeenCalledTimes(1)
|
|
112
120
|
})
|
|
113
121
|
|
|
@@ -480,7 +488,8 @@ const HERO = 'https://cdn.example.com/yoga.jpg'
|
|
|
480
488
|
const shellOf = () => screen.getByTestId(/^link-(card|attachment)$/)
|
|
481
489
|
|
|
482
490
|
describe('LinkAttachment.Composer chrome', () => {
|
|
483
|
-
it('anchors the dismiss control to the hero on a featured card', () => {
|
|
491
|
+
it('anchors the dismiss control to the hero on a featured card', async () => {
|
|
492
|
+
const user = userEvent.setup()
|
|
484
493
|
const onDismiss = vi.fn()
|
|
485
494
|
renderWithProviders(
|
|
486
495
|
<LinkAttachment.Composer
|
|
@@ -496,7 +505,7 @@ describe('LinkAttachment.Composer chrome', () => {
|
|
|
496
505
|
expect(dismiss.className).toContain('before:-inset-[10px]')
|
|
497
506
|
// Hover lifts the fill so the control is not identical to its rest state.
|
|
498
507
|
expect(dismiss.className).toContain('hover:bg-[#2a2928]')
|
|
499
|
-
|
|
508
|
+
await user.click(dismiss)
|
|
500
509
|
expect(onDismiss).toHaveBeenCalledTimes(1)
|
|
501
510
|
})
|
|
502
511
|
|
|
@@ -563,12 +572,13 @@ describe('LinkAttachment.Composer chrome', () => {
|
|
|
563
572
|
expect(screen.queryByLabelText('Dismiss attachment')).toBeNull()
|
|
564
573
|
})
|
|
565
574
|
|
|
566
|
-
it('renders the edit affordance only when a handler is given', () => {
|
|
575
|
+
it('renders the edit affordance only when a handler is given', async () => {
|
|
576
|
+
const user = userEvent.setup()
|
|
567
577
|
const onEditClick = vi.fn()
|
|
568
578
|
renderWithProviders(
|
|
569
579
|
<LinkAttachment.Composer title="Brie’s FAQ" onEditClick={onEditClick} />
|
|
570
580
|
)
|
|
571
|
-
|
|
581
|
+
await user.click(screen.getByLabelText('Edit attachment'))
|
|
572
582
|
expect(onEditClick).toHaveBeenCalledTimes(1)
|
|
573
583
|
expect(screen.queryByLabelText('Dismiss attachment')).toBeNull()
|
|
574
584
|
})
|
|
@@ -580,7 +590,8 @@ describe('LinkAttachment.Composer chrome', () => {
|
|
|
580
590
|
})
|
|
581
591
|
|
|
582
592
|
describe('LinkAttachment interactivity contract', () => {
|
|
583
|
-
it('keeps the Received shell non-interactive around playable media', () => {
|
|
593
|
+
it('keeps the Received shell non-interactive around playable media', async () => {
|
|
594
|
+
const user = userEvent.setup()
|
|
584
595
|
const onClick = vi.fn()
|
|
585
596
|
renderWithProviders(
|
|
586
597
|
<LinkAttachment.Received
|
|
@@ -594,7 +605,7 @@ describe('LinkAttachment interactivity contract', () => {
|
|
|
594
605
|
// The native controls own clicks, so the shell is neither anchor nor button.
|
|
595
606
|
const root = screen.getByTestId('link-attachment')
|
|
596
607
|
expect(root.tagName).toBe('DIV')
|
|
597
|
-
|
|
608
|
+
await user.click(root)
|
|
598
609
|
expect(onClick).not.toHaveBeenCalled()
|
|
599
610
|
})
|
|
600
611
|
|
|
@@ -642,18 +653,20 @@ describe('LinkAttachment interactivity contract', () => {
|
|
|
642
653
|
)
|
|
643
654
|
})
|
|
644
655
|
|
|
645
|
-
it('ignores the Sent onClick when there is nothing to open', () => {
|
|
656
|
+
it('ignores the Sent onClick when there is nothing to open', async () => {
|
|
657
|
+
const user = userEvent.setup()
|
|
646
658
|
const onClick = vi.fn()
|
|
647
659
|
renderWithProviders(
|
|
648
660
|
<LinkAttachment.Sent title="My Playlist" onClick={onClick} />
|
|
649
661
|
)
|
|
650
662
|
const shell = shellOf()
|
|
651
663
|
expect(shell.tagName).toBe('DIV')
|
|
652
|
-
|
|
664
|
+
await user.click(shell)
|
|
653
665
|
expect(onClick).not.toHaveBeenCalled()
|
|
654
666
|
})
|
|
655
667
|
|
|
656
|
-
it('ignores the Sent onClick around playable media', () => {
|
|
668
|
+
it('ignores the Sent onClick around playable media', async () => {
|
|
669
|
+
const user = userEvent.setup()
|
|
657
670
|
const onClick = vi.fn()
|
|
658
671
|
renderWithProviders(
|
|
659
672
|
<LinkAttachment.Sent
|
|
@@ -666,7 +679,7 @@ describe('LinkAttachment interactivity contract', () => {
|
|
|
666
679
|
)
|
|
667
680
|
const shell = shellOf()
|
|
668
681
|
expect(shell.tagName).toBe('DIV')
|
|
669
|
-
|
|
682
|
+
await user.click(shell)
|
|
670
683
|
expect(onClick).not.toHaveBeenCalled()
|
|
671
684
|
})
|
|
672
685
|
})
|
|
@@ -27,6 +27,7 @@ describe('BubbleTail', () => {
|
|
|
27
27
|
expect(tail.parentElement?.className).toBe('relative isolate w-fit')
|
|
28
28
|
expect(tail.getAttribute('aria-hidden')).toBe('true')
|
|
29
29
|
// The tail follows the card so it paints over the card's bottom edge.
|
|
30
|
+
// eslint-disable-next-line testing-library/no-node-access -- sibling placement is the deliberate visual DOM contract.
|
|
30
31
|
expect(screen.getByText('card').nextElementSibling).toBe(tail)
|
|
31
32
|
})
|
|
32
33
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { describe, expect, it, vi } from 'vitest'
|
|
3
3
|
|
|
4
|
-
import { renderWithProviders, screen
|
|
4
|
+
import { userEvent, renderWithProviders, screen } from '../../../../test/utils'
|
|
5
5
|
|
|
6
6
|
import CardBody from './CardBody'
|
|
7
7
|
|
|
@@ -154,9 +154,8 @@ describe('CardBody', () => {
|
|
|
154
154
|
)
|
|
155
155
|
const icon = screen.getByTestId('app-icon')
|
|
156
156
|
const title = screen.getByText('Spotify')
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
).toBeTruthy()
|
|
157
|
+
const content = screen.getAllByText(/^(icon|Spotify)$/)
|
|
158
|
+
expect(content.indexOf(icon)).toBeLessThan(content.indexOf(title))
|
|
160
159
|
expect(screen.getByText('Edit')).toBeInTheDocument()
|
|
161
160
|
})
|
|
162
161
|
|
|
@@ -192,7 +191,8 @@ describe('CardBody', () => {
|
|
|
192
191
|
expect(container).not.toBeEmptyDOMElement()
|
|
193
192
|
})
|
|
194
193
|
|
|
195
|
-
it('keeps the cta interactive alongside the chin text', () => {
|
|
194
|
+
it('keeps the cta interactive alongside the chin text', async () => {
|
|
195
|
+
const user = userEvent.setup()
|
|
196
196
|
const onClick = vi.fn()
|
|
197
197
|
renderWithProviders(
|
|
198
198
|
<CardBody
|
|
@@ -201,7 +201,7 @@ describe('CardBody', () => {
|
|
|
201
201
|
cta={{ label: 'View FAQs', onClick }}
|
|
202
202
|
/>
|
|
203
203
|
)
|
|
204
|
-
|
|
204
|
+
await user.click(screen.getByText('View FAQs'))
|
|
205
205
|
expect(onClick).toHaveBeenCalledTimes(1)
|
|
206
206
|
})
|
|
207
207
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { describe, expect, it, vi } from 'vitest'
|
|
3
3
|
|
|
4
|
-
import { renderWithProviders, screen
|
|
4
|
+
import { userEvent, renderWithProviders, screen } from '../../../../test/utils'
|
|
5
5
|
|
|
6
6
|
import CardCta from './CardCta'
|
|
7
7
|
|
|
@@ -37,7 +37,8 @@ describe('CardCta', () => {
|
|
|
37
37
|
['button', undefined],
|
|
38
38
|
])(
|
|
39
39
|
'fires the cta callback from the %s without bubbling to the card',
|
|
40
|
-
(_shape, href) => {
|
|
40
|
+
async (_shape, href) => {
|
|
41
|
+
const user = userEvent.setup()
|
|
41
42
|
const ctaOnClick = vi.fn()
|
|
42
43
|
const cardOnClick = vi.fn()
|
|
43
44
|
renderWithProviders(
|
|
@@ -49,7 +50,7 @@ describe('CardCta', () => {
|
|
|
49
50
|
/>
|
|
50
51
|
</div>
|
|
51
52
|
)
|
|
52
|
-
|
|
53
|
+
await user.click(screen.getByText('Act'))
|
|
53
54
|
expect(ctaOnClick).toHaveBeenCalledTimes(1)
|
|
54
55
|
expect(cardOnClick).not.toHaveBeenCalled()
|
|
55
56
|
}
|
|
@@ -58,7 +59,8 @@ describe('CardCta', () => {
|
|
|
58
59
|
it.each([
|
|
59
60
|
['anchor', 'tr.ee/faq'],
|
|
60
61
|
['button', undefined],
|
|
61
|
-
])('tolerates a %s cta with no callback', (_shape, href) => {
|
|
62
|
+
])('tolerates a %s cta with no callback', async (_shape, href) => {
|
|
63
|
+
const user = userEvent.setup()
|
|
62
64
|
const cardOnClick = vi.fn()
|
|
63
65
|
renderWithProviders(
|
|
64
66
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -- stands in for the card anchor the CTA must not bubble to
|
|
@@ -66,7 +68,7 @@ describe('CardCta', () => {
|
|
|
66
68
|
<CardCta variant="light" cta={{ label: 'Act', href }} />
|
|
67
69
|
</div>
|
|
68
70
|
)
|
|
69
|
-
|
|
71
|
+
await user.click(screen.getByText('Act'))
|
|
70
72
|
expect(cardOnClick).not.toHaveBeenCalled()
|
|
71
73
|
})
|
|
72
74
|
|
|
@@ -88,6 +90,7 @@ describe('CardCta', () => {
|
|
|
88
90
|
it('renders the label verbatim, without a wrapper span, by default', () => {
|
|
89
91
|
renderWithProviders(<CardCta variant="light" cta={{ label: 'Act' }} />)
|
|
90
92
|
const cta = screen.getByText('Act')
|
|
93
|
+
// eslint-disable-next-line testing-library/no-node-access -- media or rendered-markup internals have no user-facing query.
|
|
91
94
|
expect(cta.querySelector('span')).toBeNull()
|
|
92
95
|
})
|
|
93
96
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { describe, expect, it, vi } from 'vitest'
|
|
3
3
|
|
|
4
|
-
import { renderWithProviders, screen
|
|
4
|
+
import { userEvent, renderWithProviders, screen } from '../../../../test/utils'
|
|
5
5
|
|
|
6
6
|
import CardShell, { LOADING_LABEL } from './CardShell'
|
|
7
7
|
|
|
@@ -20,7 +20,8 @@ describe('CardShell', () => {
|
|
|
20
20
|
expect(shell.getAttribute('data-variant')).toBe('light')
|
|
21
21
|
})
|
|
22
22
|
|
|
23
|
-
it('renders an external anchor when href is set and fires onClick alongside navigation', () => {
|
|
23
|
+
it('renders an external anchor when href is set and fires onClick alongside navigation', async () => {
|
|
24
|
+
const user = userEvent.setup()
|
|
24
25
|
const onClick = vi.fn()
|
|
25
26
|
renderWithProviders(
|
|
26
27
|
<CardShell
|
|
@@ -38,11 +39,12 @@ describe('CardShell', () => {
|
|
|
38
39
|
expect(shell.getAttribute('target')).toBe('_blank')
|
|
39
40
|
expect(shell.getAttribute('rel')).toBe('noopener noreferrer')
|
|
40
41
|
expect(shell.className).toContain('focus-ring')
|
|
41
|
-
|
|
42
|
+
await user.click(shell)
|
|
42
43
|
expect(onClick).toHaveBeenCalledTimes(1)
|
|
43
44
|
})
|
|
44
45
|
|
|
45
|
-
it('renders a labelled button when onClick is set without href', () => {
|
|
46
|
+
it('renders a labelled button when onClick is set without href', async () => {
|
|
47
|
+
const user = userEvent.setup()
|
|
46
48
|
const onClick = vi.fn()
|
|
47
49
|
renderWithProviders(
|
|
48
50
|
<CardShell
|
|
@@ -58,7 +60,7 @@ describe('CardShell', () => {
|
|
|
58
60
|
expect(shell.tagName).toBe('BUTTON')
|
|
59
61
|
expect(shell.getAttribute('type')).toBe('button')
|
|
60
62
|
expect(shell.getAttribute('aria-label')).toBe('Open attachment preview')
|
|
61
|
-
|
|
63
|
+
await user.click(shell)
|
|
62
64
|
expect(onClick).toHaveBeenCalledTimes(1)
|
|
63
65
|
})
|
|
64
66
|
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { describe, expect, it, vi } from 'vitest'
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
cleanup,
|
|
6
|
-
renderWithProviders,
|
|
7
|
-
screen,
|
|
8
|
-
fireEvent,
|
|
9
|
-
} from '../../../../test/utils'
|
|
4
|
+
import { renderWithProviders, screen, fireEvent } from '../../../../test/utils'
|
|
10
5
|
|
|
11
6
|
import CardThumbnail, {
|
|
12
7
|
AUDIO_BG_CLASS,
|
|
@@ -207,7 +202,7 @@ describe('CardThumbnail', () => {
|
|
|
207
202
|
})
|
|
208
203
|
|
|
209
204
|
it('pins the corner slots and omits their wrappers when unused', () => {
|
|
210
|
-
renderWithProviders(
|
|
205
|
+
const { unmount } = renderWithProviders(
|
|
211
206
|
<CardThumbnail
|
|
212
207
|
variant="dark"
|
|
213
208
|
topLeft={<span>left</span>}
|
|
@@ -223,7 +218,7 @@ describe('CardThumbnail', () => {
|
|
|
223
218
|
expect(topRight.className).toContain('absolute right-3 top-3')
|
|
224
219
|
/* eslint-enable local/no-style-only-assertion */
|
|
225
220
|
|
|
226
|
-
|
|
221
|
+
unmount()
|
|
227
222
|
renderWithProviders(<CardThumbnail variant="dark" />)
|
|
228
223
|
expect(
|
|
229
224
|
screen.queryByTestId('card-thumbnail-top-left')
|