@linktr.ee/messaging-react 4.4.3 → 4.4.4-rc-1788190776

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.
Files changed (59) hide show
  1. package/dist/AttachmentCard-BbsjzdLJ.cjs +2 -0
  2. package/dist/AttachmentCard-BbsjzdLJ.cjs.map +1 -0
  3. package/dist/{AttachmentCard-BqwWgqvi.js → AttachmentCard-DSekwIOA.js} +35 -20
  4. package/dist/AttachmentCard-DSekwIOA.js.map +1 -0
  5. package/dist/{Card-BIbqWagz.cjs → Card-BS6Fza2a.cjs} +2 -2
  6. package/dist/{Card-BIbqWagz.cjs.map → Card-BS6Fza2a.cjs.map} +1 -1
  7. package/dist/{Card-f51K6V7Y.js → Card-BhwBkgxI.js} +2 -2
  8. package/dist/{Card-f51K6V7Y.js.map → Card-BhwBkgxI.js.map} +1 -1
  9. package/dist/index.cjs +2 -2
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.js +67 -42
  12. package/dist/index.js.map +1 -1
  13. package/package.json +3 -3
  14. package/src/components/AttachmentCard/MediaPlayer.test.tsx +62 -85
  15. package/src/components/AttachmentCard/MediaPlayer.tsx +14 -2
  16. package/src/components/AttachmentCard/Thumbnail.test.tsx +37 -46
  17. package/src/components/AttachmentCard/index.test.tsx +18 -46
  18. package/src/components/AttachmentCard/index.tsx +8 -2
  19. package/src/components/AttachmentCard/utils/icons.ts +11 -1
  20. package/src/components/Avatar/Avatar.test.tsx +37 -64
  21. package/src/components/Avatar/index.tsx +8 -1
  22. package/src/components/ChannelList/CustomChannelPreview.test.tsx +19 -19
  23. package/src/components/ChannelList/CustomChannelPreview.tsx +8 -2
  24. package/src/components/ChannelView.outbound-resolution.integration.test.tsx +25 -30
  25. package/src/components/ChannelView.test.tsx +17 -31
  26. package/src/components/ChannelView.tsx +1 -0
  27. package/src/components/CustomLinkPreviewList/CustomLinkPreviewList.test.tsx +8 -5
  28. package/src/components/CustomLinkPreviewList/index.tsx +4 -1
  29. package/src/components/CustomMessage/StreamAttachmentMessage.test.tsx +26 -21
  30. package/src/components/CustomMessage/StreamAttachmentMessage.tsx +2 -0
  31. package/src/components/CustomMessageInput/CustomMessageInput.test.tsx +43 -56
  32. package/src/components/CustomMessageInput/index.tsx +25 -5
  33. package/src/components/CustomSystemMessage/CustomSystemMessage.test.tsx +17 -23
  34. package/src/components/FaqList/FaqList.test.tsx +5 -4
  35. package/src/components/FaqList/index.tsx +6 -1
  36. package/src/components/LinkAttachment/LinkAttachment.test.tsx +70 -81
  37. package/src/components/LinkAttachment/components/_shared/BubbleTail.test.tsx +1 -0
  38. package/src/components/LinkAttachment/components/_shared/CardBody.test.tsx +20 -61
  39. package/src/components/LinkAttachment/components/_shared/CardBody.tsx +10 -2
  40. package/src/components/LinkAttachment/components/_shared/CardCta.test.tsx +1 -21
  41. package/src/components/LinkAttachment/components/_shared/CardShell.test.tsx +11 -37
  42. package/src/components/LinkAttachment/components/_shared/CardShell.tsx +5 -2
  43. package/src/components/LinkAttachment/components/_shared/CardThumbnail.test.tsx +65 -72
  44. package/src/components/LinkAttachment/components/_shared/CardThumbnail.tsx +13 -2
  45. package/src/components/MessageAttachment/MessageAttachment.test.tsx +25 -8
  46. package/src/components/MessageAttachment/_shared/CompactDocumentRow.test.tsx +5 -19
  47. package/src/components/MessageAttachment/_shared/MediaStackGrid.test.tsx +7 -9
  48. package/src/components/MessageAttachment/_shared/VideoViewer.test.tsx +9 -2
  49. package/src/components/MessageAttachment/_shared/VideoViewer.tsx +1 -0
  50. package/src/components/MessageAttachment/_shared/ViewerShell.test.tsx +2 -2
  51. package/src/components/MessageBubble/MessageBubble.test.tsx +0 -1
  52. package/src/components/RichCard/RichCard.test.tsx +24 -54
  53. package/src/components/RichCard/RichCardBody.tsx +4 -1
  54. package/src/hooks/useChannelModerationActions.ts +15 -30
  55. package/src/logging/index.test.tsx +1 -0
  56. package/src/providers/MessagingProvider.test.tsx +37 -41
  57. package/dist/AttachmentCard-BqwWgqvi.js.map +0 -1
  58. package/dist/AttachmentCard-DUrgGRsq.cjs +0 -2
  59. package/dist/AttachmentCard-DUrgGRsq.cjs.map +0 -1
@@ -97,7 +97,7 @@ const CardShell: React.FC<CardShellProps> = ({
97
97
  accentHex,
98
98
  fixedHeight = false,
99
99
  busy = false,
100
- 'data-testid': dataTestId,
100
+ 'data-testid': dataTestId = 'link-card',
101
101
  }) => {
102
102
  const isInteractive = href != null || onClick != null
103
103
  const className = classNames(
@@ -138,7 +138,10 @@ const CardShell: React.FC<CardShellProps> = ({
138
138
  : undefined
139
139
 
140
140
  const corner = topRight ? (
141
- <div className="pointer-events-auto absolute right-3 top-3 z-10">
141
+ <div
142
+ data-testid="card-shell-top-right"
143
+ className="pointer-events-auto absolute right-3 top-3 z-10"
144
+ >
142
145
  {topRight}
143
146
  </div>
144
147
  ) : null
@@ -1,7 +1,12 @@
1
1
  import React from 'react'
2
2
  import { describe, expect, it, vi } from 'vitest'
3
3
 
4
- import { renderWithProviders, screen, fireEvent } from '../../../../test/utils'
4
+ import {
5
+ cleanup,
6
+ renderWithProviders,
7
+ screen,
8
+ fireEvent,
9
+ } from '../../../../test/utils'
5
10
 
6
11
  import CardThumbnail, {
7
12
  AUDIO_BG_CLASS,
@@ -53,7 +58,7 @@ describe('AUDIO_BG_CLASS', () => {
53
58
 
54
59
  describe('CardThumbnail', () => {
55
60
  it('collapses the hero to a padded native audio player', () => {
56
- const { container } = renderWithProviders(
61
+ renderWithProviders(
57
62
  <CardThumbnail
58
63
  variant="dark"
59
64
  mimeType="audio/mpeg"
@@ -61,18 +66,17 @@ describe('CardThumbnail', () => {
61
66
  thumbnailUrl={IMAGE_SRC}
62
67
  />
63
68
  )
64
- const audio = container.querySelector('audio')
65
- expect(audio).not.toBeNull()
66
- expect(audio?.getAttribute('src')).toBe(AUDIO_SRC)
67
- expect(audio?.hasAttribute('controls')).toBe(true)
68
- expect(audio?.getAttribute('preload')).toBe('metadata')
69
+ const audio = screen.getByTestId('card-thumbnail-audio')
70
+ expect(audio).toHaveAttribute('src', AUDIO_SRC)
71
+ expect(audio).toHaveAttribute('controls')
72
+ expect(audio).toHaveAttribute('preload', 'metadata')
69
73
  // The hero region (and its artwork) is gone entirely on this branch.
70
- expect(container.querySelectorAll('img')).toHaveLength(0)
71
- expect(container.querySelector('div.flex-1')).toBeNull()
74
+ expect(screen.queryAllByRole('img', { hidden: true })).toHaveLength(0)
75
+ expect(screen.queryByTestId('card-thumbnail-hero')).not.toBeInTheDocument()
72
76
  })
73
77
 
74
78
  it('keeps the skeleton instead of an audio player while loading', () => {
75
- const { container } = renderWithProviders(
79
+ renderWithProviders(
76
80
  <CardThumbnail
77
81
  variant="dark"
78
82
  mimeType="audio/mpeg"
@@ -80,12 +84,12 @@ describe('CardThumbnail', () => {
80
84
  isLoading
81
85
  />
82
86
  )
83
- expect(container.querySelector('audio')).toBeNull()
84
- expect(container.querySelector('div.animate-pulse')).not.toBeNull()
87
+ expect(screen.queryByTestId('card-thumbnail-audio')).not.toBeInTheDocument()
88
+ expect(screen.getByTestId('card-thumbnail-skeleton')).toBeInTheDocument()
85
89
  })
86
90
 
87
91
  it('renders a video player with the poster over a black hero', () => {
88
- const { container } = renderWithProviders(
92
+ renderWithProviders(
89
93
  <CardThumbnail
90
94
  variant="light"
91
95
  mimeType="video/mp4"
@@ -95,19 +99,16 @@ describe('CardThumbnail', () => {
95
99
  onImageError={() => {}}
96
100
  />
97
101
  )
98
- const video = container.querySelector('video')
99
- expect(video?.getAttribute('src')).toBe(VIDEO_SRC)
100
- expect(video?.getAttribute('poster')).toBe(IMAGE_SRC)
101
- expect(video?.hasAttribute('controls')).toBe(true)
102
- expect(video?.getAttribute('preload')).toBe('metadata')
103
- expect(container.querySelector('div.flex-1')?.className).toContain(
104
- 'bg-black'
105
- )
102
+ const video = screen.getByTestId('card-thumbnail-video')
103
+ expect(video).toHaveAttribute('src', VIDEO_SRC)
104
+ expect(video).toHaveAttribute('poster', IMAGE_SRC)
105
+ expect(video).toHaveAttribute('controls')
106
+ expect(video).toHaveAttribute('preload', 'metadata')
106
107
  })
107
108
 
108
109
  it('probes the video poster with a hidden image that reports failures', () => {
109
110
  const onImageError = vi.fn()
110
- const { container } = renderWithProviders(
111
+ renderWithProviders(
111
112
  <CardThumbnail
112
113
  variant="light"
113
114
  mimeType="video/mp4"
@@ -116,7 +117,7 @@ describe('CardThumbnail', () => {
116
117
  onImageError={onImageError}
117
118
  />
118
119
  )
119
- const probe = container.querySelector('img')
120
+ const probe = screen.queryByRole('img', { hidden: true })
120
121
  expect(probe).not.toBeNull()
121
122
  expect(probe?.className).toContain('hidden')
122
123
  expect(probe?.getAttribute('aria-hidden')).toBe('true')
@@ -125,7 +126,7 @@ describe('CardThumbnail', () => {
125
126
  })
126
127
 
127
128
  it('skips the poster probe when there is nothing to report to', () => {
128
- const { container: noHandler } = renderWithProviders(
129
+ const view = renderWithProviders(
129
130
  <CardThumbnail
130
131
  variant="light"
131
132
  mimeType="video/mp4"
@@ -133,9 +134,10 @@ describe('CardThumbnail', () => {
133
134
  thumbnailUrl={IMAGE_SRC}
134
135
  />
135
136
  )
136
- expect(noHandler.querySelectorAll('img')).toHaveLength(0)
137
+ expect(screen.queryAllByRole('img', { hidden: true })).toHaveLength(0)
138
+ view.unmount()
137
139
 
138
- const { container: noPoster } = renderWithProviders(
140
+ renderWithProviders(
139
141
  <CardThumbnail
140
142
  variant="light"
141
143
  mimeType="video/mp4"
@@ -143,19 +145,21 @@ describe('CardThumbnail', () => {
143
145
  onImageError={() => {}}
144
146
  />
145
147
  )
146
- expect(noPoster.querySelectorAll('img')).toHaveLength(0)
147
- expect(noPoster.querySelector('video')?.getAttribute('poster')).toBeNull()
148
+ expect(screen.queryAllByRole('img', { hidden: true })).toHaveLength(0)
149
+ expect(screen.getByTestId('card-thumbnail-video')).not.toHaveAttribute(
150
+ 'poster'
151
+ )
148
152
  })
149
153
 
150
154
  it('renders the thumbnail image with the title as its alt text', () => {
151
- const { container } = renderWithProviders(
155
+ renderWithProviders(
152
156
  <CardThumbnail
153
157
  variant="light"
154
158
  thumbnailUrl={IMAGE_SRC}
155
159
  title="Cover art"
156
160
  />
157
161
  )
158
- const image = container.querySelector('img')
162
+ const image = screen.getByRole('img', { hidden: true })
159
163
  expect(image?.getAttribute('src')).toBe(IMAGE_SRC)
160
164
  expect(image?.getAttribute('alt')).toBe('Cover art')
161
165
  expect(image?.getAttribute('loading')).toBe('lazy')
@@ -164,60 +168,41 @@ describe('CardThumbnail', () => {
164
168
  })
165
169
 
166
170
  it('falls back to empty alt text when the card has no title', () => {
167
- const { container } = renderWithProviders(
171
+ renderWithProviders(
168
172
  <CardThumbnail variant="light" thumbnailUrl={IMAGE_SRC} />
169
173
  )
170
- expect(container.querySelector('img')?.getAttribute('alt')).toBe('')
174
+ expect(screen.getByRole('img', { hidden: true })).toHaveAttribute('alt', '')
171
175
  })
172
176
 
173
177
  it('reports a failing hero image to its owner', () => {
174
178
  const onImageError = vi.fn()
175
- const { container } = renderWithProviders(
179
+ renderWithProviders(
176
180
  <CardThumbnail
177
181
  variant="light"
178
182
  thumbnailUrl={IMAGE_SRC}
179
183
  onImageError={onImageError}
180
184
  />
181
185
  )
182
- fireEvent.error(container.querySelector('img') as HTMLImageElement)
186
+ fireEvent.error(screen.getByRole('img', { hidden: true }))
183
187
  expect(onImageError).toHaveBeenCalledTimes(1)
184
188
  })
185
189
 
186
- it('shows a variant-aware type-icon placeholder without a thumbnail', () => {
187
- const { container: dark } = renderWithProviders(
190
+ it('shows a type-icon placeholder instead of an image without a thumbnail', () => {
191
+ renderWithProviders(
188
192
  <CardThumbnail variant="dark" mimeType="application/pdf" />
189
193
  )
190
- expect(dark.querySelectorAll('img')).toHaveLength(0)
191
- const darkPlaceholder = dark.querySelector('div.items-center')
192
- expect(darkPlaceholder?.className).toContain('bg-white/10')
193
- expect(darkPlaceholder?.querySelector('svg')?.getAttribute('class')).toBe(
194
- 'size-16 text-white/25'
195
- )
196
-
197
- const { container: light } = renderWithProviders(
198
- <CardThumbnail variant="light" />
199
- )
200
- const lightPlaceholder = light.querySelector('div.items-center')
201
- expect(lightPlaceholder?.className).toContain('bg-black/5')
202
- expect(lightPlaceholder?.querySelector('svg')?.getAttribute('class')).toBe(
203
- 'size-16 text-black/25'
204
- )
194
+ expect(screen.queryAllByRole('img', { hidden: true })).toHaveLength(0)
195
+ expect(screen.getByTestId('card-thumbnail-placeholder')).toBeInTheDocument()
205
196
  })
206
197
 
207
- it('pulses a variant-aware block while loading, ignoring the thumbnail', () => {
208
- const { container: dark } = renderWithProviders(
198
+ it('pulses a placeholder block while loading, ignoring the thumbnail', () => {
199
+ renderWithProviders(
209
200
  <CardThumbnail variant="dark" thumbnailUrl={IMAGE_SRC} isLoading />
210
201
  )
211
- expect(dark.querySelectorAll('img')).toHaveLength(0)
212
- const bar = dark.querySelector('div.animate-pulse')
213
- expect(bar?.className).toContain('bg-white/10')
214
- expect(bar?.getAttribute('aria-hidden')).toBe('true')
215
-
216
- const { container: light } = renderWithProviders(
217
- <CardThumbnail variant="light" isLoading />
218
- )
219
- expect(light.querySelector('div.animate-pulse')?.className).toContain(
220
- 'bg-black/5'
202
+ expect(screen.queryAllByRole('img', { hidden: true })).toHaveLength(0)
203
+ expect(screen.getByTestId('card-thumbnail-skeleton')).toHaveAttribute(
204
+ 'aria-hidden',
205
+ 'true'
221
206
  )
222
207
  })
223
208
 
@@ -229,14 +214,22 @@ describe('CardThumbnail', () => {
229
214
  topRight={<span>right</span>}
230
215
  />
231
216
  )
232
- expect(screen.getByText('left').parentElement?.className).toContain(
233
- 'absolute left-3 top-3'
234
- )
235
- expect(screen.getByText('right').parentElement?.className).toContain(
236
- 'absolute right-3 top-3'
237
- )
238
-
239
- const { container } = renderWithProviders(<CardThumbnail variant="dark" />)
240
- expect(container.querySelectorAll('div.absolute')).toHaveLength(0)
217
+ const topLeft = screen.getByTestId('card-thumbnail-top-left')
218
+ const topRight = screen.getByTestId('card-thumbnail-top-right')
219
+ expect(topLeft).toHaveTextContent('left')
220
+ expect(topRight).toHaveTextContent('right')
221
+ /* eslint-disable local/no-style-only-assertion -- overlay slots: in flow they would displace the hero instead of sitting over it. */
222
+ expect(topLeft.className).toContain('absolute left-3 top-3')
223
+ expect(topRight.className).toContain('absolute right-3 top-3')
224
+ /* eslint-enable local/no-style-only-assertion */
225
+
226
+ cleanup()
227
+ renderWithProviders(<CardThumbnail variant="dark" />)
228
+ expect(
229
+ screen.queryByTestId('card-thumbnail-top-left')
230
+ ).not.toBeInTheDocument()
231
+ expect(
232
+ screen.queryByTestId('card-thumbnail-top-right')
233
+ ).not.toBeInTheDocument()
241
234
  })
242
235
  })
@@ -110,6 +110,7 @@ const CardThumbnail: React.FC<CardThumbnailProps> = ({
110
110
  <div className="p-3">
111
111
  <audio
112
112
  {...nativePlayerProps}
113
+ data-testid="card-thumbnail-audio"
113
114
  src={sourceUrl}
114
115
  controls
115
116
  preload="metadata"
@@ -123,6 +124,7 @@ const CardThumbnail: React.FC<CardThumbnailProps> = ({
123
124
 
124
125
  return (
125
126
  <div
127
+ data-testid="card-thumbnail-hero"
126
128
  className={classNames(
127
129
  // `flex-1 min-h-0` inside the shell's 250px column: the hero takes
128
130
  // every pixel the chin doesn't, which is 168px at a two-line title
@@ -138,6 +140,7 @@ const CardThumbnail: React.FC<CardThumbnailProps> = ({
138
140
  // not in either consumer's, so it would compile to nothing in
139
141
  // frontyard and profiles while looking right in this Storybook.
140
142
  <div
143
+ data-testid="card-thumbnail-skeleton"
141
144
  className={classNames(
142
145
  'h-full w-full animate-pulse',
143
146
  PLACEHOLDER_BG[variant]
@@ -147,6 +150,7 @@ const CardThumbnail: React.FC<CardThumbnailProps> = ({
147
150
  ) : isPlayableVideo ? (
148
151
  <>
149
152
  <video
153
+ data-testid="card-thumbnail-video"
150
154
  src={sourceUrl}
151
155
  poster={optimizedThumbnailUrl}
152
156
  controls
@@ -190,6 +194,7 @@ const CardThumbnail: React.FC<CardThumbnailProps> = ({
190
194
  />
191
195
  ) : (
192
196
  <div
197
+ data-testid="card-thumbnail-placeholder"
193
198
  className={classNames(
194
199
  'flex h-full w-full items-center justify-center',
195
200
  PLACEHOLDER_BG[variant]
@@ -203,12 +208,18 @@ const CardThumbnail: React.FC<CardThumbnailProps> = ({
203
208
  )}
204
209
 
205
210
  {topLeft ? (
206
- <div className="pointer-events-auto absolute left-3 top-3 z-10">
211
+ <div
212
+ data-testid="card-thumbnail-top-left"
213
+ className="pointer-events-auto absolute left-3 top-3 z-10"
214
+ >
207
215
  {topLeft}
208
216
  </div>
209
217
  ) : null}
210
218
  {topRight ? (
211
- <div className="pointer-events-auto absolute right-3 top-3 z-10">
219
+ <div
220
+ data-testid="card-thumbnail-top-right"
221
+ className="pointer-events-auto absolute right-3 top-3 z-10"
222
+ >
212
223
  {topRight}
213
224
  </div>
214
225
  ) : null}
@@ -1,7 +1,12 @@
1
1
  import React from 'react'
2
2
  import { beforeEach, describe, expect, it, vi } from 'vitest'
3
3
 
4
- import { renderWithProviders, screen, fireEvent } from '../../test/utils'
4
+ import {
5
+ fireEvent,
6
+ renderWithProviders,
7
+ screen,
8
+ within,
9
+ } from '../../test/utils'
5
10
 
6
11
  import { triggerDownload } from './_shared/triggerDownload'
7
12
 
@@ -365,6 +370,7 @@ describe('MessageAttachment.Bubble geometry overrides', () => {
365
370
  filename="file.zip"
366
371
  />
367
372
  )
373
+ // eslint-disable-next-line local/no-style-only-assertion -- the override contract above, not the values: an appended radius silently loses to the corner table.
368
374
  expect(screen.getByTestId('file-attachment')).toHaveClass(
369
375
  'w-[280px]',
370
376
  'px-2',
@@ -378,6 +384,7 @@ describe('MessageAttachment.Bubble geometry overrides', () => {
378
384
  <MessageAttachment.Image.Sent src="https://cdn.example.com/photo.jpg" />
379
385
  )
380
386
  const bubble = screen.getByTestId('image-attachment')
387
+ // eslint-disable-next-line local/no-style-only-assertion -- the override contract above, not the values: an appended radius silently loses to the corner table.
381
388
  expect(bubble).toHaveClass('w-fit', 'p-0.5', 'rounded-[20px]')
382
389
  expect(bubble.className).not.toMatch(
383
390
  /w-\[280px\]|px-2|py-2|rounded-tl-\[18px\]/
@@ -912,8 +919,10 @@ describe('MessageAttachment.Video single-card aspect', () => {
912
919
  />
913
920
  )
914
921
  expect(
915
- screen.getByTestId('video-attachment').querySelector('img')
916
- ).toBeNull()
922
+ within(screen.getByTestId('video-attachment')).queryAllByRole('img', {
923
+ hidden: true,
924
+ })
925
+ ).toHaveLength(0)
917
926
  // Clamped to MIN_ASPECT: a true 9:16 would render a 587px-tall card, so
918
927
  // portrait pins at 4:5 until design asks for a full-height card.
919
928
  expect(mediaAspect()).toBe(String(4 / 5))
@@ -1017,8 +1026,10 @@ describe('MessageAttachment.Video single-card aspect', () => {
1017
1026
  <MessageAttachment.Video.Received src="https://cdn.example.com/clip.mp4" />
1018
1027
  )
1019
1028
  expect(
1020
- screen.getByTestId('video-attachment').querySelector('img')
1021
- ).toBeNull()
1029
+ within(screen.getByTestId('video-attachment')).queryAllByRole('img', {
1030
+ hidden: true,
1031
+ })
1032
+ ).toHaveLength(0)
1022
1033
  expect(mediaAspect()).toBe(String(1))
1023
1034
  })
1024
1035
 
@@ -1142,12 +1153,18 @@ describe('MessageAttachment lazy-loading defaults', () => {
1142
1153
  // on the raw (un-optimised) source. The element being absent
1143
1154
  // while closed is therefore the only thing stopping a scrolled
1144
1155
  // thread from downloading every full-resolution original.
1145
- expect(screen.getByTestId('image-viewer').querySelector('img')).toBeNull()
1156
+ expect(
1157
+ within(screen.getByTestId('image-viewer')).queryAllByRole('img', {
1158
+ hidden: true,
1159
+ })
1160
+ ).toHaveLength(0)
1146
1161
 
1147
1162
  fireEvent.click(screen.getByLabelText('Open image 1 of 1'))
1148
1163
  expect(
1149
- screen.getByTestId('image-viewer').querySelector('img')
1150
- ).not.toBeNull()
1164
+ within(screen.getByTestId('image-viewer')).getByRole('img', {
1165
+ hidden: true,
1166
+ })
1167
+ ).toBeInTheDocument()
1151
1168
  })
1152
1169
  })
1153
1170
 
@@ -17,7 +17,7 @@ describe('CompactDocumentRow', () => {
17
17
  })
18
18
 
19
19
  it('renders metadata only when it has a value and defaults MIME type', () => {
20
- const { container, rerender } = renderWithProviders(
20
+ const { rerender } = renderWithProviders(
21
21
  <CompactDocumentRow
22
22
  variant="dark"
23
23
  filename="report.pdf"
@@ -33,7 +33,7 @@ describe('CompactDocumentRow', () => {
33
33
  />
34
34
  )
35
35
  expect(screen.queryByText(/·/)).toBeNull()
36
- const genericIconMarkup = container.querySelector('svg')?.innerHTML
36
+ expect(screen.getByTestId('type-icon-generic')).toBeInTheDocument()
37
37
  rerender(
38
38
  <CompactDocumentRow
39
39
  variant="dark"
@@ -41,14 +41,12 @@ describe('CompactDocumentRow', () => {
41
41
  mimeType="application/pdf"
42
42
  />
43
43
  )
44
- expect(container.querySelector('svg')?.innerHTML).not.toBe(
45
- genericIconMarkup
46
- )
44
+ expect(screen.getByTestId('type-icon-pdf')).toBeInTheDocument()
47
45
  })
48
46
 
49
- it('maps variant classes across metadata, icon, and activation', () => {
47
+ it('maps the variant onto the activatable body treatment', () => {
50
48
  const onActivate = vi.fn()
51
- const { container, rerender } = renderWithProviders(
49
+ const { rerender } = renderWithProviders(
52
50
  <CompactDocumentRow
53
51
  variant="dark"
54
52
  filename="report.pdf"
@@ -56,12 +54,6 @@ describe('CompactDocumentRow', () => {
56
54
  activateLabel="Open report"
57
55
  />
58
56
  )
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
57
  expect(screen.getByRole('button', { name: 'Open report' })).toHaveClass(
66
58
  'hover:bg-white/[0.04]'
67
59
  )
@@ -74,12 +66,6 @@ describe('CompactDocumentRow', () => {
74
66
  activateLabel="Open report"
75
67
  />
76
68
  )
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
69
  expect(screen.getByRole('button', { name: 'Open report' })).toHaveClass(
84
70
  'hover:bg-black/[0.04]'
85
71
  )
@@ -30,14 +30,14 @@ describe('MediaStackGrid', () => {
30
30
  })
31
31
 
32
32
  it('clamps visible tiles and places overflow on the last tile', () => {
33
- const { container, rerender } = renderWithProviders(
33
+ const { rerender } = renderWithProviders(
34
34
  <MediaStackGrid tiles={tiles(6)} maxVisible={4} />
35
35
  )
36
- expect(container.querySelectorAll('span')).toHaveLength(4)
36
+ expect(screen.getAllByText(/tile/)).toHaveLength(4)
37
37
  expect(screen.getByText('+2')).toBeInTheDocument()
38
38
  expect(screen.getByText('+2').parentElement).toHaveTextContent('tile 4')
39
39
  rerender(<MediaStackGrid tiles={tiles(2)} maxVisible={4} />)
40
- expect(container.querySelectorAll('span')).toHaveLength(2)
40
+ expect(screen.getAllByText(/tile/)).toHaveLength(2)
41
41
  expect(screen.queryByText(/^\+/)).toBeNull()
42
42
  rerender(<MediaStackGrid tiles={tiles(1)} singleAspectRatio={0.5} />)
43
43
  expect(
@@ -53,20 +53,18 @@ describe('MediaStackGrid', () => {
53
53
  ])(
54
54
  'shows overflow on the last visible tile when maxVisible is %s',
55
55
  (maxVisible, badge, lastTile) => {
56
- const { container } = renderWithProviders(
56
+ renderWithProviders(
57
57
  <MediaStackGrid tiles={tiles(5)} maxVisible={maxVisible} />
58
58
  )
59
- expect(container.querySelectorAll('span')).toHaveLength(maxVisible)
59
+ expect(screen.getAllByText(/tile/)).toHaveLength(maxVisible)
60
60
  expect(screen.getByText(badge)).toBeInTheDocument()
61
61
  expect(screen.getByText(badge).parentElement).toHaveTextContent(lastTile)
62
62
  }
63
63
  )
64
64
 
65
65
  it('shows overflow on the single visible tile', () => {
66
- const { container } = renderWithProviders(
67
- <MediaStackGrid tiles={tiles(3)} maxVisible={1} />
68
- )
69
- expect(container.querySelectorAll('span')).toHaveLength(1)
66
+ renderWithProviders(<MediaStackGrid tiles={tiles(3)} maxVisible={1} />)
67
+ expect(screen.getAllByText(/tile/)).toHaveLength(1)
70
68
  expect(screen.getByText('+2').parentElement).toHaveTextContent('tile 1')
71
69
  })
72
70
 
@@ -5,6 +5,7 @@ import {
5
5
  fireEvent,
6
6
  renderWithProviders,
7
7
  screen,
8
+ within,
8
9
  } from '../../../test/utils'
9
10
 
10
11
  import VideoViewer from './VideoViewer'
@@ -106,7 +107,9 @@ describe('VideoViewer', () => {
106
107
  // Gone once the dialog is: a `<video preload="metadata">` left in a
107
108
  // closed viewer fetches a header per bubble in the thread.
108
109
  expect(
109
- screen.getByTestId('video-viewer').querySelector('video')
110
+ within(screen.getByTestId('video-viewer')).queryByTestId(
111
+ 'video-viewer-media'
112
+ )
110
113
  ).toBeNull()
111
114
  } finally {
112
115
  vi.useRealTimers()
@@ -117,6 +120,10 @@ describe('VideoViewer', () => {
117
120
  renderWithProviders(
118
121
  <VideoViewer open={false} onClose={vi.fn()} items={[items[0]]} />
119
122
  )
120
- expect(screen.getByTestId('video-viewer').querySelector('video')).toBeNull()
123
+ expect(
124
+ within(screen.getByTestId('video-viewer')).queryByTestId(
125
+ 'video-viewer-media'
126
+ )
127
+ ).toBeNull()
121
128
  })
122
129
  })
@@ -131,6 +131,7 @@ const VideoViewer: React.FC<VideoViewerProps> = ({
131
131
  {mounted ? (
132
132
  <video
133
133
  ref={videoRef}
134
+ data-testid="video-viewer-media"
134
135
  // Forcing a key swap on item change ensures the new source
135
136
  // mounts a fresh element instead of reusing the previous
136
137
  // playback state — keeps the video paused-at-start when
@@ -123,12 +123,12 @@ describe('ViewerShell', () => {
123
123
  throw new Error('not supported')
124
124
  })
125
125
  defineDialogMethod('showModal', showModal)
126
- const { container } = renderWithProviders(
126
+ renderWithProviders(
127
127
  <ViewerShell open onClose={vi.fn()} data-testid="viewer">
128
128
  content
129
129
  </ViewerShell>
130
130
  )
131
- expect(container.querySelector('dialog')).toHaveAttribute('open')
131
+ expect(screen.getByRole('dialog')).toHaveAttribute('open')
132
132
  expect(showModal).toHaveBeenCalledOnce()
133
133
  })
134
134
 
@@ -71,6 +71,5 @@ describe('MessageBubble', () => {
71
71
  )
72
72
  const bubble = screen.getByText('wide').parentElement as HTMLElement
73
73
  expect(bubble.classList.contains('w-fit')).toBe(true)
74
- expect(bubble.className).toContain('max-w-[520px]')
75
74
  })
76
75
  })