@linktr.ee/messaging-react 4.2.0-rc-1787325710 → 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.
- package/dist/Card-Be81K6su.cjs +2 -0
- package/dist/Card-Be81K6su.cjs.map +1 -0
- package/dist/Card-BzvDr-pU.js +112 -0
- package/dist/Card-BzvDr-pU.js.map +1 -0
- package/dist/Card-D7UPq5-g.js +701 -0
- package/dist/Card-D7UPq5-g.js.map +1 -0
- package/dist/Card-DcBZqMBt.cjs +2 -0
- package/dist/Card-DcBZqMBt.cjs.map +1 -0
- package/dist/PurchaseStatusBadge-BmqAQEV9.cjs +2 -0
- package/dist/PurchaseStatusBadge-BmqAQEV9.cjs.map +1 -0
- package/dist/PurchaseStatusBadge-D1RRsubZ.js +59 -0
- package/dist/PurchaseStatusBadge-D1RRsubZ.js.map +1 -0
- package/dist/assets/index.css +1 -2
- package/dist/index-CSkr_CO5.js +6053 -0
- package/dist/index-CSkr_CO5.js.map +1 -0
- package/dist/index-CwkY7QwT.cjs +3 -0
- package/dist/index-CwkY7QwT.cjs.map +1 -0
- package/dist/index.cjs +2 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +59 -52
- package/dist/index.js +37 -4071
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +2 -2
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js +118 -106
- package/dist/testing.js.map +1 -1
- package/package.json +16 -14
- package/src/components/AttachmentCard/utils/icons.ts +7 -6
- package/src/components/Avatar/Avatar.test.tsx +0 -126
- package/src/components/Avatar/getAvatarEmoji.test.ts +0 -39
- package/src/components/ChannelList/CustomChannelPreview.test.tsx +3 -410
- package/src/components/ChannelList/index.test.tsx +1 -132
- package/src/components/ChannelView.test.tsx +3 -9
- package/src/components/CustomMessage/CustomMessage.stories.tsx +314 -6
- package/src/components/CustomMessage/CustomMessage.test.tsx +2 -56
- package/src/components/CustomMessage/MessageTag.stories.tsx +53 -17
- package/src/components/CustomMessage/MessageTag.test.tsx +40 -7
- package/src/components/CustomMessage/MessageTag.tsx +39 -7
- package/src/components/CustomMessage/SentMessageDeliveryStatus.test.tsx +7 -8
- package/src/components/CustomMessage/SentMessageDeliveryStatus.tsx +1 -2
- package/src/components/CustomMessage/StreamAttachmentMessage.test.tsx +33 -0
- package/src/components/CustomMessage/StreamAttachmentMessage.tsx +12 -5
- package/src/components/CustomMessage/index.tsx +19 -22
- package/src/components/CustomMessageInput/index.tsx +1 -1
- package/src/components/CustomSystemMessage/index.tsx +2 -3
- package/src/components/LinkAttachment/LinkAttachment.test.tsx +0 -327
- package/src/components/MessageAttachment/Audio/AudioAttachment.stories.tsx +165 -58
- package/src/components/MessageAttachment/Audio/WaveformAudioRow.test.tsx +153 -0
- package/src/components/MessageAttachment/Audio/WaveformAudioRow.tsx +450 -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 -77
- package/src/components/MessageAttachment/MessageAttachment.test.tsx +176 -74
- package/src/components/MessageAttachment/_shared/fileMeta.test.ts +0 -44
- 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/MessagingShell/MessagingShell.test.tsx +0 -20
- package/src/components/MessagingShell/index.tsx +0 -2
- package/src/components/RichCard/RichCard.test.tsx +1 -3
- package/src/index.ts +0 -1
- package/src/providers/MessagingProvider.test.tsx +1 -3
- package/src/styles.css +210 -57
- package/src/testing/createMockMessagingClient.ts +2 -2
- package/src/types.ts +0 -1
- package/dist/AttachmentCard-C-X0jLZ2.cjs +0 -2
- package/dist/AttachmentCard-C-X0jLZ2.cjs.map +0 -1
- package/dist/AttachmentCard-D2IoHdcv.js +0 -410
- package/dist/AttachmentCard-D2IoHdcv.js.map +0 -1
- package/dist/Card-C7uLQbYz.js +0 -105
- package/dist/Card-C7uLQbYz.js.map +0 -1
- package/dist/Card-DENHpLIC.cjs +0 -2
- package/dist/Card-DENHpLIC.cjs.map +0 -1
- package/dist/Card-Ds1FMcUj.cjs +0 -2
- package/dist/Card-Ds1FMcUj.cjs.map +0 -1
- package/dist/Card-EKD9NkTa.js +0 -498
- package/dist/Card-EKD9NkTa.js.map +0 -1
- package/dist/LoadingDots-BSk_UeS0.cjs +0 -2
- package/dist/LoadingDots-BSk_UeS0.cjs.map +0 -1
- package/dist/LoadingDots-B_WiXBeQ.js +0 -73
- package/dist/LoadingDots-B_WiXBeQ.js.map +0 -1
- package/dist/rolldown-runtime-BocRIvOZ.cjs +0 -1
- package/src/components/AttachmentCard/MediaPlayer.test.tsx +0 -346
- package/src/components/AttachmentCard/Thumbnail.test.tsx +0 -215
- package/src/components/AttachmentCard/index.test.tsx +0 -160
- package/src/components/ChannelList/ChannelListContext.test.tsx +0 -87
- package/src/components/CustomMessage/Locked.stories.tsx +0 -108
- package/src/components/CustomMessage/PaidMessage.stories.tsx +0 -42
- package/src/components/CustomMessage/Tip.stories.tsx +0 -48
- package/src/components/CustomMessage/TipMessage/TipMessage.stories.tsx +0 -51
- package/src/components/CustomMessage/TipMessage/TipMessage.test.tsx +0 -65
- package/src/components/CustomMessage/TipMessage/index.tsx +0 -27
- package/src/components/FaqList/FaqList.test.tsx +0 -204
- package/src/components/LinkAttachment/components/_shared/BubbleTail.test.tsx +0 -76
- package/src/components/LinkAttachment/components/_shared/CardBody.test.tsx +0 -248
- package/src/components/LinkAttachment/components/_shared/CardCta.test.tsx +0 -123
- package/src/components/LinkAttachment/components/_shared/CardShell.test.tsx +0 -193
- package/src/components/LinkAttachment/components/_shared/CardThumbnail.test.tsx +0 -242
- package/src/components/LinkAttachment/components/_shared/hex.test.ts +0 -37
- package/src/components/LinkAttachment/components/_shared/normalizeExternalHref.test.ts +0 -72
- package/src/components/LinkAttachment/components/_shared/useChinPalette.test.ts +0 -162
- package/src/components/MessageAttachment/MessageAttachment.behavior.test.tsx +0 -182
- package/src/components/MessageAttachment/_shared/Bubble.test.tsx +0 -111
- package/src/components/MessageAttachment/_shared/CarouselNav.test.tsx +0 -36
- package/src/components/MessageAttachment/_shared/CompactDocumentRow.test.tsx +0 -114
- package/src/components/MessageAttachment/_shared/DismissButton.test.tsx +0 -42
- package/src/components/MessageAttachment/_shared/DownloadAction.test.tsx +0 -168
- package/src/components/MessageAttachment/_shared/ImageViewer.test.tsx +0 -52
- package/src/components/MessageAttachment/_shared/MediaStackGrid.test.tsx +0 -69
- package/src/components/MessageAttachment/_shared/PdfViewer.test.tsx +0 -45
- package/src/components/MessageAttachment/_shared/VideoViewer.test.tsx +0 -55
- package/src/components/MessageAttachment/_shared/ViewerShell.test.tsx +0 -246
- package/src/components/MessageAttachment/_shared/triggerDownload.test.ts +0 -113
- package/src/components/MessageAttachment/_shared/useCarousel.test.tsx +0 -68
- package/src/components/MessageAttachment/_shared/viewerChromeClasses.test.ts +0 -16
- package/src/stories/customMessageStoryTemplate.tsx +0 -202
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { act, renderHook } from '@testing-library/react'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import { useChinPalette } from './useChinPalette'
|
|
5
|
-
import type { ChinPaletteInput } from './useChinPalette'
|
|
6
|
-
|
|
7
|
-
const THUMBNAIL = 'https://cdn.example.com/cover.jpg'
|
|
8
|
-
|
|
9
|
-
const render = (input: ChinPaletteInput) =>
|
|
10
|
-
renderHook((props: ChinPaletteInput) => useChinPalette(props), {
|
|
11
|
-
initialProps: input,
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
describe('useChinPalette', () => {
|
|
15
|
-
it('publishes a validated accent for a ready featured hero', () => {
|
|
16
|
-
const { result } = render({
|
|
17
|
-
variant: 'dark',
|
|
18
|
-
accentColor: '#7008E7',
|
|
19
|
-
thumbnailUrl: THUMBNAIL,
|
|
20
|
-
})
|
|
21
|
-
expect(result.current.accentHex).toBe('#7008E7')
|
|
22
|
-
expect(result.current.thumbnailUrl).toBe(THUMBNAIL)
|
|
23
|
-
expect(result.current.isLoading).toBe(false)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('hashes a bare hex and tolerates surrounding whitespace', () => {
|
|
27
|
-
const { result } = render({
|
|
28
|
-
variant: 'dark',
|
|
29
|
-
accentColor: ' 7008E7 ',
|
|
30
|
-
thumbnailUrl: THUMBNAIL,
|
|
31
|
-
})
|
|
32
|
-
expect(result.current.accentHex).toBe('#7008E7')
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it.each([
|
|
36
|
-
'rebeccapurple',
|
|
37
|
-
'rgb(112, 8, 231)',
|
|
38
|
-
'#12g',
|
|
39
|
-
'#1234',
|
|
40
|
-
'',
|
|
41
|
-
undefined,
|
|
42
|
-
])('drops the unusable accent %s', (accentColor) => {
|
|
43
|
-
const { result } = render({
|
|
44
|
-
variant: 'dark',
|
|
45
|
-
accentColor,
|
|
46
|
-
thumbnailUrl: THUMBNAIL,
|
|
47
|
-
})
|
|
48
|
-
expect(result.current.accentHex).toBeUndefined()
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('drops the accent for a classic card, which has no hero', () => {
|
|
52
|
-
const { result } = render({
|
|
53
|
-
variant: 'dark',
|
|
54
|
-
accentColor: '#7008E7',
|
|
55
|
-
layout: 'classic',
|
|
56
|
-
thumbnailUrl: THUMBNAIL,
|
|
57
|
-
})
|
|
58
|
-
expect(result.current.accentHex).toBeUndefined()
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it.each(['loading', 'failed'] as const)(
|
|
62
|
-
'drops the accent while status is %s',
|
|
63
|
-
(status) => {
|
|
64
|
-
const { result } = render({
|
|
65
|
-
variant: 'dark',
|
|
66
|
-
accentColor: '#7008E7',
|
|
67
|
-
status,
|
|
68
|
-
thumbnailUrl: THUMBNAIL,
|
|
69
|
-
})
|
|
70
|
-
expect(result.current.accentHex).toBeUndefined()
|
|
71
|
-
expect(result.current.isLoading).toBe(status === 'loading')
|
|
72
|
-
}
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
it.each([undefined, ''])(
|
|
76
|
-
'drops the accent when the hero url is %s',
|
|
77
|
-
(thumbnailUrl) => {
|
|
78
|
-
const { result } = render({
|
|
79
|
-
variant: 'dark',
|
|
80
|
-
accentColor: '#7008E7',
|
|
81
|
-
thumbnailUrl,
|
|
82
|
-
})
|
|
83
|
-
expect(result.current.accentHex).toBeUndefined()
|
|
84
|
-
}
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
it('drops the accent for playable audio, whose hero is the player', () => {
|
|
88
|
-
const { result } = render({
|
|
89
|
-
variant: 'dark',
|
|
90
|
-
accentColor: '#7008E7',
|
|
91
|
-
thumbnailUrl: THUMBNAIL,
|
|
92
|
-
mimeType: 'audio/mpeg',
|
|
93
|
-
sourceUrl: 'https://cdn.example.com/clip.mp3',
|
|
94
|
-
})
|
|
95
|
-
expect(result.current.accentHex).toBeUndefined()
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
it('keeps the accent for playable video, whose poster is the hero', () => {
|
|
99
|
-
const { result } = render({
|
|
100
|
-
variant: 'dark',
|
|
101
|
-
accentColor: '#7008E7',
|
|
102
|
-
thumbnailUrl: THUMBNAIL,
|
|
103
|
-
mimeType: 'video/mp4',
|
|
104
|
-
sourceUrl: 'https://cdn.example.com/clip.mp4',
|
|
105
|
-
})
|
|
106
|
-
expect(result.current.accentHex).toBe('#7008E7')
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('withdraws the hero and its accent once the image fails', () => {
|
|
110
|
-
const { result } = render({
|
|
111
|
-
variant: 'dark',
|
|
112
|
-
accentColor: '#7008E7',
|
|
113
|
-
thumbnailUrl: THUMBNAIL,
|
|
114
|
-
})
|
|
115
|
-
act(() => {
|
|
116
|
-
result.current.onImageError()
|
|
117
|
-
})
|
|
118
|
-
expect(result.current.thumbnailUrl).toBeUndefined()
|
|
119
|
-
expect(result.current.accentHex).toBeUndefined()
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it('gives a new hero url a fresh chance to load', () => {
|
|
123
|
-
const { result, rerender } = render({
|
|
124
|
-
variant: 'dark',
|
|
125
|
-
accentColor: '#7008E7',
|
|
126
|
-
thumbnailUrl: THUMBNAIL,
|
|
127
|
-
})
|
|
128
|
-
act(() => {
|
|
129
|
-
result.current.onImageError()
|
|
130
|
-
})
|
|
131
|
-
expect(result.current.accentHex).toBeUndefined()
|
|
132
|
-
|
|
133
|
-
rerender({
|
|
134
|
-
variant: 'dark',
|
|
135
|
-
accentColor: '#7008E7',
|
|
136
|
-
thumbnailUrl: 'https://cdn.example.com/other.jpg',
|
|
137
|
-
})
|
|
138
|
-
expect(result.current.thumbnailUrl).toBe(
|
|
139
|
-
'https://cdn.example.com/other.jpg'
|
|
140
|
-
)
|
|
141
|
-
expect(result.current.accentHex).toBe('#7008E7')
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
it.each([
|
|
145
|
-
['dark', '#FFFFFF'],
|
|
146
|
-
['light', '#040403'],
|
|
147
|
-
] as const)('inks the %s surface with %s', (variant, ink) => {
|
|
148
|
-
const { result } = render({ variant })
|
|
149
|
-
expect(result.current.chinTextColor).toBe(ink)
|
|
150
|
-
expect(result.current.surface).toBe(variant)
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it('treats the audio strip as a light surface on either side', () => {
|
|
154
|
-
const { result } = render({
|
|
155
|
-
variant: 'dark',
|
|
156
|
-
mimeType: 'audio/mpeg',
|
|
157
|
-
sourceUrl: 'https://cdn.example.com/clip.mp3',
|
|
158
|
-
})
|
|
159
|
-
expect(result.current.chinTextColor).toBe('#040403')
|
|
160
|
-
expect(result.current.surface).toBe('light')
|
|
161
|
-
})
|
|
162
|
-
})
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { fireEvent, renderWithProviders, screen } from '../../test/utils'
|
|
4
|
-
|
|
5
|
-
import { triggerDownload } from './_shared/triggerDownload'
|
|
6
|
-
import { bubbleVariantForState, bubbleGroupPositionFromStream } from './types'
|
|
7
|
-
|
|
8
|
-
import MessageAttachment from '.'
|
|
9
|
-
|
|
10
|
-
vi.mock('./_shared/triggerDownload', () => ({
|
|
11
|
-
triggerDownload: vi.fn(() => Promise.resolve()),
|
|
12
|
-
}))
|
|
13
|
-
|
|
14
|
-
const mockedDownload = vi.mocked(triggerDownload)
|
|
15
|
-
|
|
16
|
-
describe('MessageAttachment behavior', () => {
|
|
17
|
-
it('renders file rows with precedence, fallback names, and cancellation', () => {
|
|
18
|
-
const onClick = vi.fn(() => false)
|
|
19
|
-
const { rerender } = renderWithProviders(
|
|
20
|
-
<MessageAttachment.File.Sent
|
|
21
|
-
src="https://cdn.example.com/fallback.txt"
|
|
22
|
-
filename="fallback.txt"
|
|
23
|
-
items={[
|
|
24
|
-
{
|
|
25
|
-
src: 'https://cdn.example.com/first.pdf',
|
|
26
|
-
filename: 'first.pdf',
|
|
27
|
-
},
|
|
28
|
-
{ src: 'https://cdn.example.com/second.pdf' },
|
|
29
|
-
]}
|
|
30
|
-
onClick={onClick}
|
|
31
|
-
/>
|
|
32
|
-
)
|
|
33
|
-
expect(screen.getByText('first.pdf')).toBeInTheDocument()
|
|
34
|
-
expect(screen.getByText('second.pdf')).toBeInTheDocument()
|
|
35
|
-
fireEvent.click(screen.getByRole('button', { name: 'Download first.pdf' }))
|
|
36
|
-
expect(onClick).toHaveBeenCalledWith(0)
|
|
37
|
-
expect(mockedDownload).not.toHaveBeenCalled()
|
|
38
|
-
|
|
39
|
-
rerender(
|
|
40
|
-
<MessageAttachment.File.Sent src="https://cdn.example.com/fallback.txt" />
|
|
41
|
-
)
|
|
42
|
-
fireEvent.click(
|
|
43
|
-
screen.getByRole('button', { name: 'Download fallback.txt' })
|
|
44
|
-
)
|
|
45
|
-
expect(mockedDownload).toHaveBeenCalledWith(
|
|
46
|
-
'https://cdn.example.com/fallback.txt',
|
|
47
|
-
'fallback.txt'
|
|
48
|
-
)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('renders no file when inputs are absent and supports composer dismissal', () => {
|
|
52
|
-
const { container } = renderWithProviders(
|
|
53
|
-
<MessageAttachment.File.Received />
|
|
54
|
-
)
|
|
55
|
-
expect(container.firstChild).toBeNull()
|
|
56
|
-
const onDismiss = vi.fn()
|
|
57
|
-
renderWithProviders(
|
|
58
|
-
<MessageAttachment.File.Composer
|
|
59
|
-
src="https://cdn.example.com/file"
|
|
60
|
-
onDismiss={onDismiss}
|
|
61
|
-
/>
|
|
62
|
-
)
|
|
63
|
-
fireEvent.click(screen.getByRole('button', { name: 'Dismiss attachment' }))
|
|
64
|
-
expect(onDismiss).toHaveBeenCalledOnce()
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('renders image stacks, forwards loading, and supports click cancellation', () => {
|
|
68
|
-
const onClick = vi.fn(() => false)
|
|
69
|
-
renderWithProviders(
|
|
70
|
-
<MessageAttachment.Image.Sent
|
|
71
|
-
src="https://cdn.example.com/fallback.jpg"
|
|
72
|
-
items={[
|
|
73
|
-
{ src: 'https://cdn.example.com/one.jpg' },
|
|
74
|
-
{ src: 'https://cdn.example.com/two.jpg', loading: 'eager' },
|
|
75
|
-
]}
|
|
76
|
-
loading="lazy"
|
|
77
|
-
onClick={onClick}
|
|
78
|
-
/>
|
|
79
|
-
)
|
|
80
|
-
expect(screen.getAllByRole('img')[0]).toHaveAttribute('loading', 'lazy')
|
|
81
|
-
expect(screen.getAllByRole('img')).toHaveLength(2)
|
|
82
|
-
fireEvent.click(screen.getByRole('button', { name: 'Open image 2 of 2' }))
|
|
83
|
-
expect(onClick).toHaveBeenCalledWith(1)
|
|
84
|
-
expect(screen.queryByTestId('image-viewer')).not.toHaveAttribute('open')
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('opens images at the clicked index and suffixes a shared filename', () => {
|
|
88
|
-
renderWithProviders(
|
|
89
|
-
<MessageAttachment.Image.Sent
|
|
90
|
-
filename="photos.jpg"
|
|
91
|
-
items={[
|
|
92
|
-
{ src: 'https://cdn.example.com/one.jpg' },
|
|
93
|
-
{ src: 'https://cdn.example.com/two.jpg' },
|
|
94
|
-
]}
|
|
95
|
-
/>
|
|
96
|
-
)
|
|
97
|
-
fireEvent.click(screen.getByRole('button', { name: 'Open image 2 of 2' }))
|
|
98
|
-
expect(screen.getByTestId('image-viewer')).toHaveAttribute(
|
|
99
|
-
'aria-label',
|
|
100
|
-
'photos.jpg (2)'
|
|
101
|
-
)
|
|
102
|
-
expect(screen.getByRole('img', { name: 'photos.jpg (2)' })).toHaveAttribute(
|
|
103
|
-
'src',
|
|
104
|
-
'https://cdn.example.com/two.jpg'
|
|
105
|
-
)
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it('renders video poster and fallback, and wires viewer attributes', () => {
|
|
109
|
-
const onDismiss = vi.fn()
|
|
110
|
-
const { rerender } = renderWithProviders(
|
|
111
|
-
<MessageAttachment.Video.Composer
|
|
112
|
-
src="https://cdn.example.com/video.mp4"
|
|
113
|
-
poster="/poster.jpg"
|
|
114
|
-
mimeType="video/mp4"
|
|
115
|
-
preload="auto"
|
|
116
|
-
onDismiss={onDismiss}
|
|
117
|
-
/>
|
|
118
|
-
)
|
|
119
|
-
expect(
|
|
120
|
-
screen.getByRole('img', { name: 'Video 1 thumbnail' })
|
|
121
|
-
).toHaveAttribute('src', '/poster.jpg')
|
|
122
|
-
fireEvent.click(screen.getByRole('button', { name: 'Dismiss attachment' }))
|
|
123
|
-
expect(onDismiss).toHaveBeenCalledOnce()
|
|
124
|
-
rerender(
|
|
125
|
-
<MessageAttachment.Video.Sent
|
|
126
|
-
items={[{ src: 'https://cdn.example.com/video.mp4' }]}
|
|
127
|
-
/>
|
|
128
|
-
)
|
|
129
|
-
expect(screen.getByLabelText('Play video 1 of 1')).toBeInTheDocument()
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('supports PDF titles, download actions, dismiss, and viewer index', () => {
|
|
133
|
-
const onDismiss = vi.fn()
|
|
134
|
-
renderWithProviders(
|
|
135
|
-
<MessageAttachment.Pdf.Composer
|
|
136
|
-
src="https://cdn.example.com/report.pdf"
|
|
137
|
-
title="Quarterly report"
|
|
138
|
-
onDismiss={onDismiss}
|
|
139
|
-
/>
|
|
140
|
-
)
|
|
141
|
-
expect(screen.getByText('Quarterly report')).toBeInTheDocument()
|
|
142
|
-
fireEvent.click(screen.getByRole('button', { name: 'Dismiss attachment' }))
|
|
143
|
-
expect(onDismiss).toHaveBeenCalledOnce()
|
|
144
|
-
|
|
145
|
-
renderWithProviders(
|
|
146
|
-
<MessageAttachment.Pdf.Sent
|
|
147
|
-
items={[
|
|
148
|
-
{ src: 'https://cdn.example.com/one.pdf' },
|
|
149
|
-
{ src: 'https://cdn.example.com/two.pdf', filename: 'two.pdf' },
|
|
150
|
-
]}
|
|
151
|
-
/>
|
|
152
|
-
)
|
|
153
|
-
fireEvent.click(screen.getByRole('button', { name: 'Open two.pdf' }))
|
|
154
|
-
expect(screen.getAllByTestId('pdf-viewer').at(-1)).toHaveAttribute(
|
|
155
|
-
'aria-label',
|
|
156
|
-
'two.pdf'
|
|
157
|
-
)
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
it('maps state and stream grouping contracts', () => {
|
|
161
|
-
expect(bubbleVariantForState('composer')).toBe('dark')
|
|
162
|
-
expect(bubbleVariantForState('sent')).toBe('dark')
|
|
163
|
-
expect(bubbleVariantForState('received')).toBe('light')
|
|
164
|
-
expect(bubbleGroupPositionFromStream({})).toBe('single')
|
|
165
|
-
expect(
|
|
166
|
-
bubbleGroupPositionFromStream({
|
|
167
|
-
groupedByUser: true,
|
|
168
|
-
firstOfGroup: true,
|
|
169
|
-
endOfGroup: true,
|
|
170
|
-
})
|
|
171
|
-
).toBe('single')
|
|
172
|
-
expect(
|
|
173
|
-
bubbleGroupPositionFromStream({ groupedByUser: true, firstOfGroup: true })
|
|
174
|
-
).toBe('first')
|
|
175
|
-
expect(
|
|
176
|
-
bubbleGroupPositionFromStream({ groupedByUser: true, endOfGroup: true })
|
|
177
|
-
).toBe('end')
|
|
178
|
-
expect(bubbleGroupPositionFromStream({ groupedByUser: true })).toBe(
|
|
179
|
-
'middle'
|
|
180
|
-
)
|
|
181
|
-
})
|
|
182
|
-
})
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { renderWithProviders, screen } from '../../../test/utils'
|
|
4
|
-
|
|
5
|
-
import Bubble from './Bubble'
|
|
6
|
-
|
|
7
|
-
describe('Bubble', () => {
|
|
8
|
-
it.each([
|
|
9
|
-
[undefined, false],
|
|
10
|
-
[null, false],
|
|
11
|
-
['', false],
|
|
12
|
-
['caption', true],
|
|
13
|
-
[0, true],
|
|
14
|
-
])('renders caption %j according to its value', (text, present) => {
|
|
15
|
-
renderWithProviders(
|
|
16
|
-
<Bubble variant="dark" text={text} data-testid="bubble">
|
|
17
|
-
<span>child</span>
|
|
18
|
-
</Bubble>
|
|
19
|
-
)
|
|
20
|
-
const bubble = screen.getByTestId('bubble')
|
|
21
|
-
if (present) expect(bubble.querySelector('p')).not.toBeNull()
|
|
22
|
-
else expect(bubble.querySelector('p')).toBeNull()
|
|
23
|
-
expect(bubble).toHaveClass(present ? 'pb-0' : 'py-2')
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('maps variants, group positions, borders, and forwarded content', () => {
|
|
27
|
-
const { rerender } = renderWithProviders(
|
|
28
|
-
<Bubble
|
|
29
|
-
variant="dark"
|
|
30
|
-
groupPosition="middle"
|
|
31
|
-
data-testid="bubble"
|
|
32
|
-
className="custom"
|
|
33
|
-
>
|
|
34
|
-
child
|
|
35
|
-
</Bubble>
|
|
36
|
-
)
|
|
37
|
-
let bubble = screen.getByTestId('bubble')
|
|
38
|
-
expect(bubble).toHaveAttribute('data-group-position', 'middle')
|
|
39
|
-
expect(bubble).toHaveClass('custom')
|
|
40
|
-
expect(bubble).toHaveTextContent('child')
|
|
41
|
-
const darkMiddle = bubble.className
|
|
42
|
-
|
|
43
|
-
rerender(
|
|
44
|
-
<Bubble
|
|
45
|
-
variant="light"
|
|
46
|
-
groupPosition="middle"
|
|
47
|
-
data-testid="bubble"
|
|
48
|
-
className="custom"
|
|
49
|
-
>
|
|
50
|
-
child
|
|
51
|
-
</Bubble>
|
|
52
|
-
)
|
|
53
|
-
bubble = screen.getByTestId('bubble')
|
|
54
|
-
const lightMiddle = bubble.className
|
|
55
|
-
expect(lightMiddle).not.toBe(darkMiddle)
|
|
56
|
-
|
|
57
|
-
rerender(
|
|
58
|
-
<Bubble
|
|
59
|
-
variant="light"
|
|
60
|
-
groupPosition="first"
|
|
61
|
-
data-testid="bubble"
|
|
62
|
-
className="custom"
|
|
63
|
-
>
|
|
64
|
-
child
|
|
65
|
-
</Bubble>
|
|
66
|
-
)
|
|
67
|
-
bubble = screen.getByTestId('bubble')
|
|
68
|
-
expect(bubble.className).not.toBe(lightMiddle)
|
|
69
|
-
const lightFirstBordered = bubble.className
|
|
70
|
-
|
|
71
|
-
rerender(
|
|
72
|
-
<Bubble
|
|
73
|
-
variant="light"
|
|
74
|
-
bordered={false}
|
|
75
|
-
groupPosition="first"
|
|
76
|
-
data-testid="bubble"
|
|
77
|
-
className="custom"
|
|
78
|
-
>
|
|
79
|
-
child
|
|
80
|
-
</Bubble>
|
|
81
|
-
)
|
|
82
|
-
bubble = screen.getByTestId('bubble')
|
|
83
|
-
const lightFirstBorderless = bubble.className
|
|
84
|
-
expect(lightFirstBorderless).not.toBe(lightFirstBordered)
|
|
85
|
-
expect(lightFirstBordered.split(/\s+/)).toEqual(
|
|
86
|
-
expect.arrayContaining(lightFirstBorderless.split(/\s+/))
|
|
87
|
-
)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('lets an explicit radius replace the group corner table', () => {
|
|
91
|
-
const radiusClassName = 'rounded-[20px]'
|
|
92
|
-
const widthClassName = 'w-fit'
|
|
93
|
-
const paddingClassName = 'p-0.5'
|
|
94
|
-
renderWithProviders(
|
|
95
|
-
<Bubble
|
|
96
|
-
variant="light"
|
|
97
|
-
groupPosition="middle"
|
|
98
|
-
radiusClassName={radiusClassName}
|
|
99
|
-
widthClassName={widthClassName}
|
|
100
|
-
paddingClassName={paddingClassName}
|
|
101
|
-
data-testid="bubble"
|
|
102
|
-
>
|
|
103
|
-
child
|
|
104
|
-
</Bubble>
|
|
105
|
-
)
|
|
106
|
-
const bubble = screen.getByTestId('bubble')
|
|
107
|
-
expect(bubble).toHaveClass(radiusClassName, widthClassName)
|
|
108
|
-
expect(bubble).toHaveClass(paddingClassName)
|
|
109
|
-
expect(bubble.className).not.toMatch(/rounded-tl-\[4px\]/)
|
|
110
|
-
})
|
|
111
|
-
})
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { fireEvent, renderWithProviders, screen } from '../../../test/utils'
|
|
4
|
-
|
|
5
|
-
import CarouselNav from './CarouselNav'
|
|
6
|
-
import { VIEWER_NAV_CLASS } from './viewerChromeClasses'
|
|
7
|
-
|
|
8
|
-
describe('CarouselNav', () => {
|
|
9
|
-
it('uses default labels and independent handlers', () => {
|
|
10
|
-
const onPrev = vi.fn()
|
|
11
|
-
const onNext = vi.fn()
|
|
12
|
-
renderWithProviders(<CarouselNav onPrev={onPrev} onNext={onNext} />)
|
|
13
|
-
|
|
14
|
-
const prev = screen.getByRole('button', { name: 'Previous' })
|
|
15
|
-
const next = screen.getByRole('button', { name: 'Next' })
|
|
16
|
-
expect(prev).toHaveClass(VIEWER_NAV_CLASS, 'left-4')
|
|
17
|
-
expect(next).toHaveClass(VIEWER_NAV_CLASS, 'right-4')
|
|
18
|
-
fireEvent.click(prev)
|
|
19
|
-
fireEvent.click(next)
|
|
20
|
-
expect(onPrev).toHaveBeenCalledOnce()
|
|
21
|
-
expect(onNext).toHaveBeenCalledOnce()
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('supports custom accessible labels', () => {
|
|
25
|
-
renderWithProviders(
|
|
26
|
-
<CarouselNav
|
|
27
|
-
onPrev={vi.fn()}
|
|
28
|
-
onNext={vi.fn()}
|
|
29
|
-
prevLabel="Previous image"
|
|
30
|
-
nextLabel="Next image"
|
|
31
|
-
/>
|
|
32
|
-
)
|
|
33
|
-
expect(screen.getByRole('button', { name: 'Previous image' })).toBeVisible()
|
|
34
|
-
expect(screen.getByRole('button', { name: 'Next image' })).toBeVisible()
|
|
35
|
-
})
|
|
36
|
-
})
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { fireEvent, renderWithProviders, screen } from '../../../test/utils'
|
|
4
|
-
|
|
5
|
-
import CompactDocumentRow from './CompactDocumentRow'
|
|
6
|
-
|
|
7
|
-
describe('CompactDocumentRow', () => {
|
|
8
|
-
it('resolves title from title, filename, then File', () => {
|
|
9
|
-
const { rerender } = renderWithProviders(
|
|
10
|
-
<CompactDocumentRow variant="dark" title="Custom" filename="name.pdf" />
|
|
11
|
-
)
|
|
12
|
-
expect(screen.getByText('Custom')).toBeInTheDocument()
|
|
13
|
-
rerender(<CompactDocumentRow variant="dark" filename="name.pdf" />)
|
|
14
|
-
expect(screen.getByText('name.pdf')).toBeInTheDocument()
|
|
15
|
-
rerender(<CompactDocumentRow variant="dark" />)
|
|
16
|
-
expect(screen.getByText('File')).toBeInTheDocument()
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('renders metadata only when it has a value and defaults MIME type', () => {
|
|
20
|
-
const { container, rerender } = renderWithProviders(
|
|
21
|
-
<CompactDocumentRow
|
|
22
|
-
variant="dark"
|
|
23
|
-
filename="report.pdf"
|
|
24
|
-
fileSize={1024}
|
|
25
|
-
/>
|
|
26
|
-
)
|
|
27
|
-
expect(screen.getByText('PDF · 1.0 KB')).toBeInTheDocument()
|
|
28
|
-
rerender(
|
|
29
|
-
<CompactDocumentRow
|
|
30
|
-
variant="dark"
|
|
31
|
-
filename="report"
|
|
32
|
-
mimeType="application/octet-stream"
|
|
33
|
-
/>
|
|
34
|
-
)
|
|
35
|
-
expect(screen.queryByText(/·/)).toBeNull()
|
|
36
|
-
const genericIconMarkup = container.querySelector('svg')?.innerHTML
|
|
37
|
-
rerender(
|
|
38
|
-
<CompactDocumentRow
|
|
39
|
-
variant="dark"
|
|
40
|
-
filename="report.pdf"
|
|
41
|
-
mimeType="application/pdf"
|
|
42
|
-
/>
|
|
43
|
-
)
|
|
44
|
-
expect(container.querySelector('svg')?.innerHTML).not.toBe(
|
|
45
|
-
genericIconMarkup
|
|
46
|
-
)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('maps variant classes across metadata, icon, and activation', () => {
|
|
50
|
-
const onActivate = vi.fn()
|
|
51
|
-
const { container, rerender } = renderWithProviders(
|
|
52
|
-
<CompactDocumentRow
|
|
53
|
-
variant="dark"
|
|
54
|
-
filename="report.pdf"
|
|
55
|
-
onActivate={onActivate}
|
|
56
|
-
activateLabel="Open report"
|
|
57
|
-
/>
|
|
58
|
-
)
|
|
59
|
-
const darkMetadata = screen.getByText('PDF').className
|
|
60
|
-
const darkIconTile = container.querySelector('div[aria-hidden]')
|
|
61
|
-
const darkTileClasses = darkIconTile?.className
|
|
62
|
-
const darkIconClasses = darkIconTile
|
|
63
|
-
?.querySelector('svg')
|
|
64
|
-
?.getAttribute('class')
|
|
65
|
-
expect(screen.getByRole('button', { name: 'Open report' })).toHaveClass(
|
|
66
|
-
'hover:bg-white/[0.04]'
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
rerender(
|
|
70
|
-
<CompactDocumentRow
|
|
71
|
-
variant="light"
|
|
72
|
-
filename="report.pdf"
|
|
73
|
-
onActivate={onActivate}
|
|
74
|
-
activateLabel="Open report"
|
|
75
|
-
/>
|
|
76
|
-
)
|
|
77
|
-
expect(screen.getByText('PDF').className).not.toBe(darkMetadata)
|
|
78
|
-
const lightIconTile = container.querySelector('div[aria-hidden]')
|
|
79
|
-
expect(lightIconTile?.className).not.toBe(darkTileClasses)
|
|
80
|
-
expect(lightIconTile?.querySelector('svg')?.getAttribute('class')).not.toBe(
|
|
81
|
-
darkIconClasses
|
|
82
|
-
)
|
|
83
|
-
expect(screen.getByRole('button', { name: 'Open report' })).toHaveClass(
|
|
84
|
-
'hover:bg-black/[0.04]'
|
|
85
|
-
)
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
it('activates its body without nesting the trailing action', () => {
|
|
89
|
-
const onActivate = vi.fn()
|
|
90
|
-
renderWithProviders(
|
|
91
|
-
<CompactDocumentRow
|
|
92
|
-
variant="dark"
|
|
93
|
-
filename="report.pdf"
|
|
94
|
-
onActivate={onActivate}
|
|
95
|
-
activateLabel="Open report"
|
|
96
|
-
trailingAction={<button type="button">Download</button>}
|
|
97
|
-
/>
|
|
98
|
-
)
|
|
99
|
-
const body = screen.getByRole('button', { name: 'Open report' })
|
|
100
|
-
expect(body).toBeInTheDocument()
|
|
101
|
-
expect(body).not.toContainElement(
|
|
102
|
-
screen.getByRole('button', { name: 'Download' })
|
|
103
|
-
)
|
|
104
|
-
fireEvent.click(body)
|
|
105
|
-
expect(onActivate).toHaveBeenCalledOnce()
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it('renders a plain body when activation is omitted', () => {
|
|
109
|
-
renderWithProviders(
|
|
110
|
-
<CompactDocumentRow variant="dark" filename="report.pdf" />
|
|
111
|
-
)
|
|
112
|
-
expect(screen.queryByRole('button', { name: /report/ })).toBeNull()
|
|
113
|
-
})
|
|
114
|
-
})
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { fireEvent, renderWithProviders, screen } from '../../../test/utils'
|
|
4
|
-
|
|
5
|
-
import DismissButton from './DismissButton'
|
|
6
|
-
|
|
7
|
-
describe('DismissButton', () => {
|
|
8
|
-
it('uses the default label, invokes the handler, and stops propagation', () => {
|
|
9
|
-
const onClick = vi.fn()
|
|
10
|
-
const outerClick = vi.fn()
|
|
11
|
-
renderWithProviders(
|
|
12
|
-
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
|
13
|
-
<div onClick={outerClick}>
|
|
14
|
-
<DismissButton onClick={onClick} />
|
|
15
|
-
</div>
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
fireEvent.click(screen.getByRole('button', { name: 'Dismiss attachment' }))
|
|
19
|
-
expect(onClick).toHaveBeenCalledOnce()
|
|
20
|
-
expect(outerClick).not.toHaveBeenCalled()
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it('supports a custom label and inline styling', () => {
|
|
24
|
-
const { rerender } = renderWithProviders(
|
|
25
|
-
<DismissButton onClick={vi.fn()} ariaLabel="Remove file" />
|
|
26
|
-
)
|
|
27
|
-
const overlayClasses = screen.getByRole('button', {
|
|
28
|
-
name: 'Remove file',
|
|
29
|
-
}).className
|
|
30
|
-
|
|
31
|
-
rerender(
|
|
32
|
-
<DismissButton
|
|
33
|
-
onClick={vi.fn()}
|
|
34
|
-
ariaLabel="Remove file"
|
|
35
|
-
variant="inline"
|
|
36
|
-
/>
|
|
37
|
-
)
|
|
38
|
-
const button = screen.getByRole('button', { name: 'Remove file' })
|
|
39
|
-
expect(button).toHaveClass('hover:bg-white/25')
|
|
40
|
-
expect(button.className).not.toBe(overlayClasses)
|
|
41
|
-
})
|
|
42
|
-
})
|