@linktr.ee/messaging-react 4.2.0-rc-1787325521 → 4.2.0-rc-1787599724
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{Card-BYJyA83X.js → Card-D7UPq5-g.js} +2 -2
- package/dist/{Card-BYJyA83X.js.map → Card-D7UPq5-g.js.map} +1 -1
- package/dist/{Card-BplJtZao.cjs → Card-DcBZqMBt.cjs} +2 -2
- package/dist/{Card-BplJtZao.cjs.map → Card-DcBZqMBt.cjs.map} +1 -1
- package/dist/assets/index.css +1 -1
- package/dist/{index-BUC9vqhJ.js → index-CSkr_CO5.js} +2089 -1952
- 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 +1 -1
- package/dist/index.d.ts +59 -78
- package/dist/index.js +26 -30
- package/dist/testing.cjs +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js +2 -2
- package/dist/testing.js.map +1 -1
- package/package.json +13 -11
- 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.stories.tsx +0 -4
- 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 -4
- package/src/providers/MessagingProvider.test.tsx +2 -130
- package/src/providers/MessagingProvider.tsx +15 -26
- package/src/styles.css +210 -57
- package/src/testing/createMockMessagingClient.ts +2 -2
- package/src/types.ts +0 -9
- package/dist/index-BUC9vqhJ.js.map +0 -1
- package/dist/index-BkqyKPJP.cjs +0 -3
- package/dist/index-BkqyKPJP.cjs.map +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/logging/index.test.tsx +0 -136
- package/src/logging/index.tsx +0 -48
- 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
|
-
})
|