@linktr.ee/messaging-react 4.4.3-rc-1788164552 → 4.4.3-rc-1788166409
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/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/AttachmentCard/MediaPlayer.test.tsx +29 -31
- package/src/components/AttachmentCard/Thumbnail.test.tsx +52 -44
- package/src/components/AttachmentCard/index.test.tsx +28 -24
- package/src/components/Avatar/Avatar.test.tsx +90 -80
- package/src/components/ChannelList/CustomChannelPreview.test.tsx +6 -6
- package/src/components/CustomMessageInput/CustomMessageInput.test.tsx +8 -8
- package/src/components/LinkAttachment/LinkAttachment.test.tsx +28 -18
- package/src/components/LinkAttachment/components/_shared/CardBody.test.tsx +11 -8
- package/src/components/LinkAttachment/components/_shared/CardThumbnail.test.tsx +12 -12
- package/src/components/MessageAttachment/MessageAttachment.test.tsx +23 -8
- package/src/components/MessageAttachment/_shared/VideoViewer.test.tsx +9 -2
- package/src/components/MessageAttachment/_shared/VideoViewer.tsx +1 -0
- package/src/components/MessageAttachment/_shared/ViewerShell.test.tsx +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linktr.ee/messaging-react",
|
|
3
|
-
"version": "4.4.3-rc-
|
|
3
|
+
"version": "4.4.3-rc-1788166409",
|
|
4
4
|
"description": "React messaging components built on messaging-core for web applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"chromatic:local": "yarn storybook:build && chromatic"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@linktr.ee/messaging-core": "2.6.0-rc-
|
|
54
|
-
"@linktr.ee/messaging-taxonomy": "0.11.0-rc-
|
|
53
|
+
"@linktr.ee/messaging-core": "2.6.0-rc-1788166409",
|
|
54
|
+
"@linktr.ee/messaging-taxonomy": "0.11.0-rc-1788166409",
|
|
55
55
|
"@phosphor-icons/react": "^2.1.10"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
@@ -67,26 +67,30 @@ describe('MediaPlayer', () => {
|
|
|
67
67
|
})
|
|
68
68
|
|
|
69
69
|
it('keeps an audio poster after canPlay but hides a video poster', () => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
70
|
+
{
|
|
71
|
+
const view = renderWithProviders(
|
|
72
|
+
<MediaPlayer source={SOURCE} mimeType="audio/mpeg" poster={POSTER} />
|
|
73
|
+
)
|
|
74
|
+
const audioElement = view.container.querySelector(
|
|
75
|
+
'audio'
|
|
76
|
+
) as HTMLAudioElement
|
|
77
|
+
expect(view.container.querySelectorAll('img')).toHaveLength(1)
|
|
78
|
+
fireEvent.canPlay(audioElement)
|
|
79
|
+
expect(view.container.querySelectorAll('img')).toHaveLength(1)
|
|
80
|
+
view.unmount()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
const view = renderWithProviders(
|
|
85
|
+
<MediaPlayer source={SOURCE} mimeType="video/mp4" poster={POSTER} />
|
|
86
|
+
)
|
|
87
|
+
const videoElement = view.container.querySelector(
|
|
88
|
+
'video'
|
|
89
|
+
) as HTMLVideoElement
|
|
90
|
+
expect(view.container.querySelectorAll('img')).toHaveLength(1)
|
|
91
|
+
fireEvent.canPlay(videoElement)
|
|
92
|
+
expect(view.container.querySelectorAll('img')).toHaveLength(0)
|
|
93
|
+
}
|
|
90
94
|
})
|
|
91
95
|
|
|
92
96
|
it('uses a type icon as the initial placeholder when no poster is provided', async () => {
|
|
@@ -156,13 +160,9 @@ describe('MediaPlayer', () => {
|
|
|
156
160
|
const root = container.firstElementChild as HTMLElement
|
|
157
161
|
|
|
158
162
|
fireEvent.click(root)
|
|
159
|
-
await
|
|
160
|
-
expect(screen.getByRole('button', { name: 'Pause' })).toBeInTheDocument()
|
|
161
|
-
)
|
|
163
|
+
await screen.findByRole('button', { name: 'Pause' })
|
|
162
164
|
fireEvent.click(root)
|
|
163
|
-
await
|
|
164
|
-
expect(screen.getByRole('button', { name: 'Play' })).toBeInTheDocument()
|
|
165
|
-
)
|
|
165
|
+
await screen.findByRole('button', { name: 'Play' })
|
|
166
166
|
expect(play).toHaveBeenCalledTimes(1)
|
|
167
167
|
|
|
168
168
|
const onContainerClick = vi.fn()
|
|
@@ -223,9 +223,7 @@ describe('MediaPlayer', () => {
|
|
|
223
223
|
const button = screen.getByRole('button', { name: 'Play' })
|
|
224
224
|
|
|
225
225
|
fireEvent.click(button)
|
|
226
|
-
await
|
|
227
|
-
expect(screen.getByRole('button', { name: 'Pause' })).toBeInTheDocument()
|
|
228
|
-
)
|
|
226
|
+
await screen.findByRole('button', { name: 'Pause' })
|
|
229
227
|
expect(container.firstElementChild).toBeInTheDocument()
|
|
230
228
|
})
|
|
231
229
|
|
|
@@ -310,7 +308,7 @@ describe('MediaPlayer', () => {
|
|
|
310
308
|
})
|
|
311
309
|
|
|
312
310
|
it('renders the progress-only track without controls when requested', () => {
|
|
313
|
-
|
|
311
|
+
renderWithProviders(
|
|
314
312
|
<MediaPlayer
|
|
315
313
|
source={SOURCE}
|
|
316
314
|
mimeType="video/mp4"
|
|
@@ -323,7 +321,7 @@ describe('MediaPlayer', () => {
|
|
|
323
321
|
screen.getByRole('slider', { name: 'Playback position' })
|
|
324
322
|
).toBeInTheDocument()
|
|
325
323
|
expect(screen.queryByRole('button', { name: 'Play' })).toBeNull()
|
|
326
|
-
expect(
|
|
324
|
+
expect(screen.getAllByRole('slider')).toHaveLength(1)
|
|
327
325
|
})
|
|
328
326
|
|
|
329
327
|
it('uses natural video dimensions after metadata and aspect-video before then', () => {
|
|
@@ -147,55 +147,63 @@ describe('AttachmentThumbnail', () => {
|
|
|
147
147
|
})
|
|
148
148
|
|
|
149
149
|
it('renders a document icon placeholder without a thumbnail', () => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
150
|
+
{
|
|
151
|
+
const view = renderWithProviders(
|
|
152
|
+
<AttachmentThumbnail
|
|
153
|
+
mimeType="application/pdf"
|
|
154
|
+
variant="dark"
|
|
155
|
+
title="PDF"
|
|
156
|
+
/>
|
|
157
|
+
)
|
|
158
|
+
const darkIcon = view.container.querySelector('svg') as SVGElement
|
|
159
|
+
expect(darkIcon).toHaveClass('size-12', 'text-white/20')
|
|
160
|
+
expect(view.container.firstElementChild).toHaveClass('bg-white/10')
|
|
161
|
+
view.unmount()
|
|
162
|
+
}
|
|
161
163
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
{
|
|
165
|
+
const view = renderWithProviders(
|
|
166
|
+
<AttachmentThumbnail mimeType="application/pdf" variant="light" />
|
|
167
|
+
)
|
|
168
|
+
const lightIcon = view.container.querySelector('svg') as SVGElement
|
|
169
|
+
expect(lightIcon).toHaveClass('size-12', 'text-black/20')
|
|
170
|
+
expect(view.container.firstElementChild).toHaveClass('bg-black/5')
|
|
171
|
+
}
|
|
168
172
|
})
|
|
169
173
|
|
|
170
174
|
it('renders the poster-only branch with variant-specific shell and alt fallback', () => {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
175
|
+
{
|
|
176
|
+
const view = renderWithProviders(
|
|
177
|
+
<AttachmentThumbnail
|
|
178
|
+
mimeType="image/jpeg"
|
|
179
|
+
thumbnailUrl={LINKTREE_THUMBNAIL}
|
|
180
|
+
title="Poster"
|
|
181
|
+
variant="dark"
|
|
182
|
+
/>
|
|
183
|
+
)
|
|
184
|
+
const darkImage = screen.getByRole('img', { name: 'Poster' })
|
|
185
|
+
expect(darkImage).toHaveAttribute('src', OPTIMIZED_THUMBNAIL)
|
|
186
|
+
expect(view.container.firstElementChild).toHaveClass(
|
|
187
|
+
'aspect-video',
|
|
188
|
+
'bg-white/10'
|
|
189
|
+
)
|
|
190
|
+
view.unmount()
|
|
191
|
+
}
|
|
186
192
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
193
|
+
{
|
|
194
|
+
const view = renderWithProviders(
|
|
195
|
+
<AttachmentThumbnail
|
|
196
|
+
mimeType="image/jpeg"
|
|
197
|
+
thumbnailUrl="https://external.example/poster.jpg"
|
|
198
|
+
variant="light"
|
|
199
|
+
/>
|
|
200
|
+
)
|
|
201
|
+
expect(screen.getByRole('img')).toHaveAttribute('alt', '')
|
|
202
|
+
expect(view.container.firstElementChild).toHaveClass(
|
|
203
|
+
'aspect-video',
|
|
204
|
+
'bg-black/5'
|
|
205
|
+
)
|
|
206
|
+
}
|
|
199
207
|
})
|
|
200
208
|
|
|
201
209
|
it('renders the empty branch with a light placeholder', () => {
|
|
@@ -9,29 +9,33 @@ const thumbnail = <div data-testid="thumbnail">thumbnail</div>
|
|
|
9
9
|
|
|
10
10
|
describe('AttachmentCard', () => {
|
|
11
11
|
it('applies the dark and light root surfaces and type icon colors', () => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
{
|
|
13
|
+
const view = renderWithProviders(
|
|
14
|
+
<AttachmentCard
|
|
15
|
+
variant="dark"
|
|
16
|
+
thumbnail={thumbnail}
|
|
17
|
+
mimeType="application/pdf"
|
|
18
|
+
title="Dark file"
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
expect(screen.getByText('Dark file')).toBeInTheDocument()
|
|
22
|
+
expect(view.container.firstElementChild).toHaveClass('bg-[#1e2330]')
|
|
23
|
+
expect(view.container.querySelector('svg')).toHaveClass('text-white/55')
|
|
24
|
+
view.unmount()
|
|
25
|
+
}
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
{
|
|
28
|
+
const view = renderWithProviders(
|
|
29
|
+
<AttachmentCard
|
|
30
|
+
variant="light"
|
|
31
|
+
thumbnail={thumbnail}
|
|
32
|
+
mimeType="application/pdf"
|
|
33
|
+
title="Light file"
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
expect(view.container.firstElementChild).toHaveClass('bg-white')
|
|
37
|
+
expect(view.container.querySelector('svg')).toHaveClass('text-black/55')
|
|
38
|
+
}
|
|
35
39
|
})
|
|
36
40
|
|
|
37
41
|
it('uses the dark placeholder title and dims it when title is absent', () => {
|
|
@@ -132,7 +136,7 @@ describe('AttachmentCard', () => {
|
|
|
132
136
|
)
|
|
133
137
|
expect(container.querySelectorAll('.pointer-events-auto')).toHaveLength(2)
|
|
134
138
|
|
|
135
|
-
const
|
|
139
|
+
const view = renderWithProviders(
|
|
136
140
|
<AttachmentCard
|
|
137
141
|
variant="light"
|
|
138
142
|
thumbnail={thumbnail}
|
|
@@ -140,7 +144,7 @@ describe('AttachmentCard', () => {
|
|
|
140
144
|
/>
|
|
141
145
|
)
|
|
142
146
|
expect(
|
|
143
|
-
|
|
147
|
+
view.container.querySelectorAll('.pointer-events-auto')
|
|
144
148
|
).toHaveLength(0)
|
|
145
149
|
})
|
|
146
150
|
|
|
@@ -42,100 +42,110 @@ describe('Avatar', () => {
|
|
|
42
42
|
)
|
|
43
43
|
|
|
44
44
|
it('does not show a star by default but shows one when starred', () => {
|
|
45
|
-
|
|
46
|
-
<Avatar id="user-1" name="Alice" />
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
{
|
|
46
|
+
const view = renderWithProviders(<Avatar id="user-1" name="Alice" />)
|
|
47
|
+
expect(view.container.querySelector('svg')).not.toBeInTheDocument()
|
|
48
|
+
}
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
{
|
|
51
|
+
const view = renderWithProviders(
|
|
52
|
+
<Avatar id="user-1" name="Alice" starred />
|
|
53
|
+
)
|
|
54
|
+
expect(view.container.querySelector('svg')).toBeInTheDocument()
|
|
55
|
+
}
|
|
54
56
|
})
|
|
55
57
|
|
|
56
58
|
it('uses a squircle by default and a circle when explicitly requested', () => {
|
|
57
|
-
|
|
58
|
-
<Avatar id="user-1" name="Alice" />
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
{
|
|
60
|
+
const view = renderWithProviders(<Avatar id="user-1" name="Alice" />)
|
|
61
|
+
const squircleElements = view.container.querySelectorAll(
|
|
62
|
+
'[style*="border-radius"]'
|
|
63
|
+
)
|
|
64
|
+
expect(squircleElements).toHaveLength(3)
|
|
65
|
+
squircleElements.forEach((element) =>
|
|
66
|
+
expect(element).toHaveStyle({ borderRadius: '1rem' })
|
|
67
|
+
)
|
|
68
|
+
}
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
70
|
+
{
|
|
71
|
+
const view = renderWithProviders(
|
|
72
|
+
<Avatar id="user-1" name="Alice" shape="circle" />
|
|
73
|
+
)
|
|
74
|
+
const circleElements = view.container.querySelectorAll(
|
|
75
|
+
'[style*="border-radius"]'
|
|
76
|
+
)
|
|
77
|
+
expect(circleElements).toHaveLength(3)
|
|
78
|
+
circleElements.forEach((element) =>
|
|
79
|
+
expect(element).toHaveStyle({ borderRadius: '50%' })
|
|
80
|
+
)
|
|
81
|
+
}
|
|
78
82
|
})
|
|
79
83
|
|
|
80
84
|
it('only applies the AI ring when dmAgentEnabled is true', () => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
{
|
|
86
|
+
const view = renderWithProviders(
|
|
87
|
+
<Avatar id="user-1" name="Alice" size={40} />
|
|
88
|
+
)
|
|
89
|
+
expect(
|
|
90
|
+
view.container.querySelector('[data-testid="avatar-ring"]')
|
|
91
|
+
).not.toHaveStyle({
|
|
92
|
+
borderWidth: '2px',
|
|
93
|
+
borderStyle: 'solid',
|
|
94
|
+
})
|
|
95
|
+
}
|
|
90
96
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
{
|
|
98
|
+
const view = renderWithProviders(
|
|
99
|
+
<Avatar id="user-1" name="Alice" size={40} dmAgentEnabled />
|
|
100
|
+
)
|
|
101
|
+
expect(
|
|
102
|
+
view.container.querySelector('[data-testid="avatar-ring"]')
|
|
103
|
+
).toHaveStyle({
|
|
104
|
+
borderWidth: '2px',
|
|
105
|
+
borderStyle: 'solid',
|
|
106
|
+
borderColor: 'var(--AI-Gradient, #7F22FE)',
|
|
107
|
+
boxShadow: 'inset 0 1px 2px 0 rgba(255, 255, 255, 0.5)',
|
|
108
|
+
})
|
|
109
|
+
}
|
|
102
110
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
111
|
+
{
|
|
112
|
+
const view = renderWithProviders(
|
|
113
|
+
<Avatar id="user-1" name="Alice" size={20} dmAgentEnabled />
|
|
114
|
+
)
|
|
115
|
+
expect(
|
|
116
|
+
view.container.querySelector('[data-testid="avatar-ring"]')
|
|
117
|
+
).toHaveStyle({ borderWidth: '1px' })
|
|
118
|
+
}
|
|
109
119
|
})
|
|
110
120
|
|
|
111
121
|
it('renders an image instead of the emoji fallback when image is provided', () => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
'transition-colors'
|
|
129
|
-
)
|
|
122
|
+
{
|
|
123
|
+
const view = renderWithProviders(<Avatar id="" name="Alice" />)
|
|
124
|
+
expect(screen.queryByRole('img')).not.toBeInTheDocument()
|
|
125
|
+
expect(view.container).toHaveTextContent('🍎')
|
|
126
|
+
expect(view.container.querySelector('.avatar-fallback')).toHaveClass(
|
|
127
|
+
'avatar-fallback',
|
|
128
|
+
'flex',
|
|
129
|
+
'h-full',
|
|
130
|
+
'w-full',
|
|
131
|
+
'items-center',
|
|
132
|
+
'justify-center',
|
|
133
|
+
'font-semibold',
|
|
134
|
+
'select-none',
|
|
135
|
+
'transition-colors'
|
|
136
|
+
)
|
|
137
|
+
}
|
|
130
138
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
'
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
+
{
|
|
140
|
+
const view = renderWithProviders(
|
|
141
|
+
<Avatar id="" name="Alice" image="https://example.com/avatar.png" />
|
|
142
|
+
)
|
|
143
|
+
expect(screen.getByRole('img')).toHaveAttribute(
|
|
144
|
+
'src',
|
|
145
|
+
'https://example.com/avatar.png'
|
|
146
|
+
)
|
|
147
|
+
expect(view.container).not.toHaveTextContent('🍎')
|
|
148
|
+
}
|
|
139
149
|
})
|
|
140
150
|
|
|
141
151
|
it('applies the fallback background color and forwards custom classes', () => {
|
|
@@ -141,12 +141,12 @@ describe('CustomChannelPreview', () => {
|
|
|
141
141
|
it('selects the channel by click and activation keys only', () => {
|
|
142
142
|
const channel = createMockChannel([])
|
|
143
143
|
const onChannelSelect = vi.fn()
|
|
144
|
-
|
|
144
|
+
renderWithProviders(
|
|
145
145
|
<ChannelListProvider value={{ onChannelSelect, debug: false }}>
|
|
146
146
|
<CustomChannelPreview channel={channel} />
|
|
147
147
|
</ChannelListProvider>
|
|
148
148
|
)
|
|
149
|
-
const row =
|
|
149
|
+
const row = screen.getByRole('button')
|
|
150
150
|
|
|
151
151
|
fireEvent.click(row)
|
|
152
152
|
fireEvent.keyDown(row, { key: 'Enter' })
|
|
@@ -213,11 +213,11 @@ describe('CustomChannelPreview', () => {
|
|
|
213
213
|
image: 'https://example.com/alice.png',
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
const {
|
|
216
|
+
const { rerender } = renderWithProviders(
|
|
217
217
|
<CustomChannelPreview {...defaultProps} channel={channel} />
|
|
218
218
|
)
|
|
219
|
-
expect(
|
|
220
|
-
expect(
|
|
219
|
+
expect(screen.getAllByRole('img', { hidden: true })).toHaveLength(1)
|
|
220
|
+
expect(screen.getByRole('img', { hidden: true })).toHaveAttribute(
|
|
221
221
|
'src',
|
|
222
222
|
'https://example.com/alice.png'
|
|
223
223
|
)
|
|
@@ -229,7 +229,7 @@ describe('CustomChannelPreview', () => {
|
|
|
229
229
|
rerender(
|
|
230
230
|
<CustomChannelPreview {...defaultProps} channel={channel} unread={1} />
|
|
231
231
|
)
|
|
232
|
-
expect(
|
|
232
|
+
expect(screen.queryAllByRole('img', { hidden: true })).toHaveLength(0)
|
|
233
233
|
})
|
|
234
234
|
|
|
235
235
|
it.each([
|
|
@@ -359,13 +359,13 @@ describe('CustomMessageInput', () => {
|
|
|
359
359
|
vi.unstubAllGlobals()
|
|
360
360
|
})
|
|
361
361
|
|
|
362
|
-
const
|
|
362
|
+
const mountCard = () => {
|
|
363
363
|
const { container } = renderWithProviders(<CustomMessageInput />)
|
|
364
364
|
return container.querySelector('.central-container') as HTMLElement
|
|
365
365
|
}
|
|
366
366
|
|
|
367
367
|
it('caps the card against the surface height and scrolls it as one region', () => {
|
|
368
|
-
const card =
|
|
368
|
+
const card = mountCard()
|
|
369
369
|
|
|
370
370
|
expect(card).toHaveClass(
|
|
371
371
|
'max-h-[calc(var(--messaging-surface-height,100dvh)*0.5)]',
|
|
@@ -384,7 +384,7 @@ describe('CustomMessageInput', () => {
|
|
|
384
384
|
})
|
|
385
385
|
|
|
386
386
|
it('lets the textarea grow to fit rather than scrolling its own overflow', () => {
|
|
387
|
-
const card =
|
|
387
|
+
const card = mountCard()
|
|
388
388
|
|
|
389
389
|
// TextareaComposer defaults an absent maxRows to 1, so this has to be set.
|
|
390
390
|
expect(screen.getByTestId('textarea-composer')).toHaveAttribute(
|
|
@@ -403,7 +403,7 @@ describe('CustomMessageInput', () => {
|
|
|
403
403
|
})
|
|
404
404
|
|
|
405
405
|
it('leaves plain-text pastes to the browser but lets file pastes reach Stream', () => {
|
|
406
|
-
|
|
406
|
+
mountCard()
|
|
407
407
|
|
|
408
408
|
// Stream's paste handler is dispatched from React's root, above this
|
|
409
409
|
// card, so a listener further up stands in for it: the event reaching
|
|
@@ -428,7 +428,7 @@ describe('CustomMessageInput', () => {
|
|
|
428
428
|
})
|
|
429
429
|
|
|
430
430
|
it('detects a pasted file via clipboardData.items even when .files is empty (Safari)', () => {
|
|
431
|
-
|
|
431
|
+
mountCard()
|
|
432
432
|
|
|
433
433
|
// Safari never populates clipboardData.files on paste, even for an
|
|
434
434
|
// image — only .items has it. Checking .files here would silently drop
|
|
@@ -448,7 +448,7 @@ describe('CustomMessageInput', () => {
|
|
|
448
448
|
})
|
|
449
449
|
|
|
450
450
|
it('scrolls the input back into view when the card shrinks under it', () => {
|
|
451
|
-
const card =
|
|
451
|
+
const card = mountCard()
|
|
452
452
|
// jsdom has no layout, so stand in for a card whose cap now cuts the
|
|
453
453
|
// input off below the fold.
|
|
454
454
|
Object.defineProperty(card, 'scrollHeight', {
|
|
@@ -463,7 +463,7 @@ describe('CustomMessageInput', () => {
|
|
|
463
463
|
})
|
|
464
464
|
|
|
465
465
|
it('leaves the scroll position alone when nothing in the composer is focused', () => {
|
|
466
|
-
const card =
|
|
466
|
+
const card = mountCard()
|
|
467
467
|
Object.defineProperty(card, 'scrollHeight', {
|
|
468
468
|
configurable: true,
|
|
469
469
|
value: 380,
|
|
@@ -478,7 +478,7 @@ describe('CustomMessageInput', () => {
|
|
|
478
478
|
})
|
|
479
479
|
|
|
480
480
|
it('sticks the send button to the bottom of the scroll, at its resting inset', () => {
|
|
481
|
-
|
|
481
|
+
mountCard()
|
|
482
482
|
|
|
483
483
|
// The offset must match the wrapper's own margin: sticky resolves against
|
|
484
484
|
// the border box, so at bottom-0 the button would lose that inset and sit
|