@linktr.ee/messaging-react 4.4.7 → 4.4.8-rc-1788251276
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/{AttachmentCard-DSekwIOA.js → AttachmentCard-BaZk77fe.js} +136 -125
- package/dist/AttachmentCard-BaZk77fe.js.map +1 -0
- package/dist/AttachmentCard-ZpYg622K.cjs +2 -0
- package/dist/AttachmentCard-ZpYg622K.cjs.map +1 -0
- package/dist/{Card-BS6Fza2a.cjs → Card-4irOjGaI.cjs} +2 -2
- package/dist/{Card-BS6Fza2a.cjs.map → Card-4irOjGaI.cjs.map} +1 -1
- package/dist/{Card-BhwBkgxI.js → Card-BdyQhysm.js} +2 -2
- package/dist/{Card-BhwBkgxI.js.map → Card-BdyQhysm.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +915 -907
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/AttachmentCard/MediaPlayer.test.tsx +9 -5
- package/src/components/AttachmentCard/MediaPlayer.tsx +3 -5
- package/src/components/AttachmentCard/Thumbnail.test.tsx +4 -0
- package/src/components/ChannelActionsMenu/ChannelActionsMenu.test.tsx +18 -10
- package/src/components/ChannelActionsMenu/index.tsx +13 -4
- package/src/components/ChannelList/ChannelListContext.test.tsx +7 -5
- package/src/components/ChannelList/CustomChannelPreview.test.tsx +7 -5
- package/src/components/ChannelList/index.test.tsx +1 -0
- package/src/components/ChannelView.test.tsx +20 -13
- package/src/components/CustomLinkPreviewList/CustomLinkPreviewList.test.tsx +4 -3
- package/src/components/CustomMessage/CustomMessage.test.tsx +6 -5
- package/src/components/CustomMessage/MessageTag.test.tsx +6 -0
- package/src/components/CustomMessage/SentMessageDeliveryStatus.test.tsx +19 -9
- package/src/components/CustomMessage/StreamAttachmentMessage.test.tsx +12 -5
- package/src/components/CustomMessage/TipMessage/TipMessage.test.tsx +3 -0
- package/src/components/CustomMessageInput/CustomMessageInput.test.tsx +16 -7
- package/src/components/FaqList/FaqList.test.tsx +20 -12
- package/src/components/LinkAttachment/LinkAttachment.test.tsx +27 -14
- package/src/components/LinkAttachment/components/_shared/BubbleTail.test.tsx +1 -0
- package/src/components/LinkAttachment/components/_shared/CardBody.test.tsx +6 -6
- package/src/components/LinkAttachment/components/_shared/CardCta.test.tsx +8 -5
- package/src/components/LinkAttachment/components/_shared/CardShell.test.tsx +7 -5
- package/src/components/LinkAttachment/components/_shared/CardThumbnail.test.tsx +3 -8
- package/src/components/MessageAttachment/MessageAttachment.behavior.test.tsx +22 -15
- package/src/components/MessageAttachment/MessageAttachment.test.tsx +117 -60
- package/src/components/MessageAttachment/_shared/Bubble.test.tsx +2 -0
- package/src/components/MessageAttachment/_shared/CarouselNav.test.tsx +5 -4
- package/src/components/MessageAttachment/_shared/CompactDocumentRow.test.tsx +4 -3
- package/src/components/MessageAttachment/_shared/DismissButton.test.tsx +4 -3
- package/src/components/MessageAttachment/_shared/DownloadAction.test.tsx +8 -5
- package/src/components/MessageAttachment/_shared/DownloadAction.tsx +4 -10
- package/src/components/MessageAttachment/_shared/ImageViewer.test.tsx +6 -3
- package/src/components/MessageAttachment/_shared/MediaStackGrid.test.tsx +11 -4
- package/src/components/MessageAttachment/_shared/PdfViewer.test.tsx +5 -3
- package/src/components/MessageAttachment/_shared/VideoViewer.test.tsx +12 -3
- package/src/components/MessageAttachment/_shared/ViewerShell.test.tsx +13 -6
- package/src/components/MessageAttachment/_shared/useCarousel.test.tsx +10 -4
- package/src/components/MessageBubble/MessageBubble.test.tsx +8 -0
- package/src/components/RichCard/RichCard.test.tsx +10 -2
- package/src/components/_shared/Spinner.test.tsx +75 -0
- package/src/components/_shared/Spinner.tsx +51 -0
- package/dist/AttachmentCard-BbsjzdLJ.cjs +0 -2
- package/dist/AttachmentCard-BbsjzdLJ.cjs.map +0 -1
- package/dist/AttachmentCard-DSekwIOA.js.map +0 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { userEvent, renderWithProviders, screen } from '../../../test/utils'
|
|
4
4
|
|
|
5
5
|
import CarouselNav from './CarouselNav'
|
|
6
6
|
import { VIEWER_NAV_CLASS } from './viewerChromeClasses'
|
|
7
7
|
|
|
8
8
|
describe('CarouselNav', () => {
|
|
9
|
-
it('uses default labels and independent handlers', () => {
|
|
9
|
+
it('uses default labels and independent handlers', async () => {
|
|
10
|
+
const user = userEvent.setup()
|
|
10
11
|
const onPrev = vi.fn()
|
|
11
12
|
const onNext = vi.fn()
|
|
12
13
|
renderWithProviders(<CarouselNav onPrev={onPrev} onNext={onNext} />)
|
|
@@ -15,8 +16,8 @@ describe('CarouselNav', () => {
|
|
|
15
16
|
const next = screen.getByRole('button', { name: 'Next' })
|
|
16
17
|
expect(prev).toHaveClass(VIEWER_NAV_CLASS, 'left-4')
|
|
17
18
|
expect(next).toHaveClass(VIEWER_NAV_CLASS, 'right-4')
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
await user.click(prev)
|
|
20
|
+
await user.click(next)
|
|
20
21
|
expect(onPrev).toHaveBeenCalledOnce()
|
|
21
22
|
expect(onNext).toHaveBeenCalledOnce()
|
|
22
23
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { userEvent, renderWithProviders, screen } from '../../../test/utils'
|
|
4
4
|
|
|
5
5
|
import CompactDocumentRow from './CompactDocumentRow'
|
|
6
6
|
|
|
@@ -71,7 +71,8 @@ describe('CompactDocumentRow', () => {
|
|
|
71
71
|
)
|
|
72
72
|
})
|
|
73
73
|
|
|
74
|
-
it('activates its body without nesting the trailing action', () => {
|
|
74
|
+
it('activates its body without nesting the trailing action', async () => {
|
|
75
|
+
const user = userEvent.setup()
|
|
75
76
|
const onActivate = vi.fn()
|
|
76
77
|
renderWithProviders(
|
|
77
78
|
<CompactDocumentRow
|
|
@@ -87,7 +88,7 @@ describe('CompactDocumentRow', () => {
|
|
|
87
88
|
expect(body).not.toContainElement(
|
|
88
89
|
screen.getByRole('button', { name: 'Download' })
|
|
89
90
|
)
|
|
90
|
-
|
|
91
|
+
await user.click(body)
|
|
91
92
|
expect(onActivate).toHaveBeenCalledOnce()
|
|
92
93
|
})
|
|
93
94
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { userEvent, renderWithProviders, screen } from '../../../test/utils'
|
|
4
4
|
|
|
5
5
|
import DismissButton from './DismissButton'
|
|
6
6
|
|
|
7
7
|
describe('DismissButton', () => {
|
|
8
|
-
it('uses the default label, invokes the handler, and stops propagation', () => {
|
|
8
|
+
it('uses the default label, invokes the handler, and stops propagation', async () => {
|
|
9
|
+
const user = userEvent.setup()
|
|
9
10
|
const onClick = vi.fn()
|
|
10
11
|
const outerClick = vi.fn()
|
|
11
12
|
renderWithProviders(
|
|
@@ -15,7 +16,7 @@ describe('DismissButton', () => {
|
|
|
15
16
|
</div>
|
|
16
17
|
)
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
await user.click(screen.getByRole('button', { name: 'Dismiss attachment' }))
|
|
19
20
|
expect(onClick).toHaveBeenCalledOnce()
|
|
20
21
|
expect(outerClick).not.toHaveBeenCalled()
|
|
21
22
|
})
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
fireEvent,
|
|
5
4
|
renderWithProviders,
|
|
6
5
|
screen,
|
|
7
6
|
waitFor,
|
|
7
|
+
userEvent,
|
|
8
8
|
} from '../../../test/utils'
|
|
9
9
|
|
|
10
10
|
import DownloadAction from './DownloadAction'
|
|
@@ -83,6 +83,7 @@ describe('DownloadAction', () => {
|
|
|
83
83
|
)
|
|
84
84
|
|
|
85
85
|
it('stops propagation and calls the download once', async () => {
|
|
86
|
+
const user = userEvent.setup()
|
|
86
87
|
const outerClick = vi.fn()
|
|
87
88
|
renderWithProviders(
|
|
88
89
|
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
|
@@ -94,7 +95,7 @@ describe('DownloadAction', () => {
|
|
|
94
95
|
/>
|
|
95
96
|
</div>
|
|
96
97
|
)
|
|
97
|
-
|
|
98
|
+
await user.click(screen.getByRole('button', { name: 'Download' }))
|
|
98
99
|
await waitFor(() =>
|
|
99
100
|
expect(mockedTriggerDownload).toHaveBeenCalledWith(
|
|
100
101
|
'/file.pdf',
|
|
@@ -106,6 +107,7 @@ describe('DownloadAction', () => {
|
|
|
106
107
|
})
|
|
107
108
|
|
|
108
109
|
it('disables repeated clicks while downloading and settles on success', async () => {
|
|
110
|
+
const user = userEvent.setup()
|
|
109
111
|
let resolve: () => void = () => undefined
|
|
110
112
|
mockedTriggerDownload.mockReturnValueOnce(
|
|
111
113
|
new Promise<void>((done) => {
|
|
@@ -117,8 +119,8 @@ describe('DownloadAction', () => {
|
|
|
117
119
|
<DownloadAction url="/file.pdf" onTriggered={onTriggered} />
|
|
118
120
|
)
|
|
119
121
|
const button = screen.getByRole('button', { name: 'Download' })
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
await user.click(button)
|
|
123
|
+
await user.click(button)
|
|
122
124
|
expect(button).toBeDisabled()
|
|
123
125
|
expect(mockedTriggerDownload).toHaveBeenCalledOnce()
|
|
124
126
|
resolve()
|
|
@@ -127,13 +129,14 @@ describe('DownloadAction', () => {
|
|
|
127
129
|
})
|
|
128
130
|
|
|
129
131
|
it('settles and notifies even when downloading rejects', async () => {
|
|
132
|
+
const user = userEvent.setup()
|
|
130
133
|
mockedTriggerDownload.mockRejectedValueOnce(new Error('offline'))
|
|
131
134
|
const onTriggered = vi.fn()
|
|
132
135
|
renderWithProviders(
|
|
133
136
|
<DownloadAction url="/file.pdf" onTriggered={onTriggered} />
|
|
134
137
|
)
|
|
135
138
|
const button = screen.getByRole('button', { name: 'Download' })
|
|
136
|
-
|
|
139
|
+
await user.click(button)
|
|
137
140
|
await waitFor(() => expect(button).toBeEnabled())
|
|
138
141
|
expect(onTriggered).toHaveBeenCalledOnce()
|
|
139
142
|
})
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
import classNames from 'classnames'
|
|
7
7
|
import React, { useCallback, useState } from 'react'
|
|
8
8
|
|
|
9
|
+
import Spinner from '../../_shared/Spinner'
|
|
10
|
+
|
|
9
11
|
import { triggerDownload } from './triggerDownload'
|
|
10
12
|
import { VIEWER_ACTION_CLASS } from './viewerChromeClasses'
|
|
11
13
|
|
|
@@ -105,11 +107,7 @@ const DownloadAction: React.FC<DownloadActionProps> = ({
|
|
|
105
107
|
)}
|
|
106
108
|
>
|
|
107
109
|
{busy ? (
|
|
108
|
-
<
|
|
109
|
-
className="size-4 animate-spin"
|
|
110
|
-
weight="bold"
|
|
111
|
-
aria-hidden
|
|
112
|
-
/>
|
|
110
|
+
<Spinner aria-hidden />
|
|
113
111
|
) : (
|
|
114
112
|
<DownloadSimpleIcon
|
|
115
113
|
className="size-5 shrink-0"
|
|
@@ -156,11 +154,7 @@ const DownloadAction: React.FC<DownloadActionProps> = ({
|
|
|
156
154
|
)}
|
|
157
155
|
>
|
|
158
156
|
{busy ? (
|
|
159
|
-
<
|
|
160
|
-
className="size-4 animate-spin"
|
|
161
|
-
weight="bold"
|
|
162
|
-
aria-hidden
|
|
163
|
-
/>
|
|
157
|
+
<Spinner aria-hidden />
|
|
164
158
|
) : (
|
|
165
159
|
<DownloadSimpleIcon {...iconProps} aria-hidden />
|
|
166
160
|
)}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { userEvent, renderWithProviders, screen } from '../../../test/utils'
|
|
4
4
|
|
|
5
5
|
import ImageViewer from './ImageViewer'
|
|
6
6
|
|
|
@@ -14,6 +14,7 @@ describe('ImageViewer', () => {
|
|
|
14
14
|
const { container } = renderWithProviders(
|
|
15
15
|
<ImageViewer open onClose={vi.fn()} items={[]} />
|
|
16
16
|
)
|
|
17
|
+
// eslint-disable-next-line testing-library/no-node-access -- the null-rendering DOM contract has no semantic query.
|
|
17
18
|
expect(container.firstChild).toBeNull()
|
|
18
19
|
})
|
|
19
20
|
|
|
@@ -29,6 +30,7 @@ describe('ImageViewer', () => {
|
|
|
29
30
|
expect(
|
|
30
31
|
screen
|
|
31
32
|
.getByTestId('image-viewer')
|
|
33
|
+
// eslint-disable-next-line testing-library/no-node-access -- media or rendered-markup internals have no user-facing query.
|
|
32
34
|
.querySelector('button[aria-label="Download one.jpg"]')
|
|
33
35
|
).toBeInTheDocument()
|
|
34
36
|
rerender(<ImageViewer open onClose={vi.fn()} items={[items[0]]} />)
|
|
@@ -36,7 +38,8 @@ describe('ImageViewer', () => {
|
|
|
36
38
|
expect(screen.getByRole('img')).toHaveAttribute('draggable', 'false')
|
|
37
39
|
})
|
|
38
40
|
|
|
39
|
-
it('renders counter and navigation only for multiple items', () => {
|
|
41
|
+
it('renders counter and navigation only for multiple items', async () => {
|
|
42
|
+
const user = userEvent.setup()
|
|
40
43
|
renderWithProviders(
|
|
41
44
|
<ImageViewer open onClose={vi.fn()} items={items} initialIndex={99} />
|
|
42
45
|
)
|
|
@@ -45,7 +48,7 @@ describe('ImageViewer', () => {
|
|
|
45
48
|
screen.getByRole('button', { name: 'Previous image' })
|
|
46
49
|
).toBeInTheDocument()
|
|
47
50
|
expect(screen.getByRole('img')).toHaveAttribute('src', items[1].src)
|
|
48
|
-
|
|
51
|
+
await user.click(screen.getByRole('button', { name: 'Previous image' }))
|
|
49
52
|
expect(screen.getByText('1 / 2')).toBeInTheDocument()
|
|
50
53
|
expect(screen.getByRole('img')).toHaveAttribute('src', items[0].src)
|
|
51
54
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { userEvent, renderWithProviders, screen } from '../../../test/utils'
|
|
4
4
|
|
|
5
5
|
import MediaStackGrid from './MediaStackGrid'
|
|
6
6
|
|
|
@@ -13,6 +13,7 @@ const tiles = (count: number) =>
|
|
|
13
13
|
describe('MediaStackGrid', () => {
|
|
14
14
|
it('renders nothing for empty tiles', () => {
|
|
15
15
|
const { container } = renderWithProviders(<MediaStackGrid tiles={[]} />)
|
|
16
|
+
// eslint-disable-next-line testing-library/no-node-access -- the null-rendering DOM contract has no semantic query.
|
|
16
17
|
expect(container.firstChild).toBeNull()
|
|
17
18
|
})
|
|
18
19
|
|
|
@@ -25,6 +26,7 @@ describe('MediaStackGrid', () => {
|
|
|
25
26
|
const { container } = renderWithProviders(
|
|
26
27
|
<MediaStackGrid tiles={tiles(count)} className="custom-grid" />
|
|
27
28
|
)
|
|
29
|
+
// eslint-disable-next-line testing-library/no-node-access -- the component shell wrapper has no semantic query.
|
|
28
30
|
expect(container.firstElementChild).toHaveClass(layoutClass, 'custom-grid')
|
|
29
31
|
expect(screen.getAllByText(/tile/)).toHaveLength(count)
|
|
30
32
|
})
|
|
@@ -35,12 +37,14 @@ describe('MediaStackGrid', () => {
|
|
|
35
37
|
)
|
|
36
38
|
expect(screen.getAllByText(/tile/)).toHaveLength(4)
|
|
37
39
|
expect(screen.getByText('+2')).toBeInTheDocument()
|
|
40
|
+
// eslint-disable-next-line testing-library/no-node-access -- the wrapper class is a deliberate DOM contract with no semantic query.
|
|
38
41
|
expect(screen.getByText('+2').parentElement).toHaveTextContent('tile 4')
|
|
39
42
|
rerender(<MediaStackGrid tiles={tiles(2)} maxVisible={4} />)
|
|
40
43
|
expect(screen.getAllByText(/tile/)).toHaveLength(2)
|
|
41
44
|
expect(screen.queryByText(/^\+/)).toBeNull()
|
|
42
45
|
rerender(<MediaStackGrid tiles={tiles(1)} singleAspectRatio={0.5} />)
|
|
43
46
|
expect(
|
|
47
|
+
// eslint-disable-next-line testing-library/no-node-access -- the component wrapper class has no semantic query.
|
|
44
48
|
screen.getByText('tile 1').closest('div')?.parentElement
|
|
45
49
|
).toHaveStyle({
|
|
46
50
|
aspectRatio: '0.5',
|
|
@@ -58,6 +62,7 @@ describe('MediaStackGrid', () => {
|
|
|
58
62
|
)
|
|
59
63
|
expect(screen.getAllByText(/tile/)).toHaveLength(maxVisible)
|
|
60
64
|
expect(screen.getByText(badge)).toBeInTheDocument()
|
|
65
|
+
// eslint-disable-next-line testing-library/no-node-access -- the wrapper class is a deliberate DOM contract with no semantic query.
|
|
61
66
|
expect(screen.getByText(badge).parentElement).toHaveTextContent(lastTile)
|
|
62
67
|
}
|
|
63
68
|
)
|
|
@@ -65,6 +70,7 @@ describe('MediaStackGrid', () => {
|
|
|
65
70
|
it('shows overflow on the single visible tile', () => {
|
|
66
71
|
renderWithProviders(<MediaStackGrid tiles={tiles(3)} maxVisible={1} />)
|
|
67
72
|
expect(screen.getAllByText(/tile/)).toHaveLength(1)
|
|
73
|
+
// eslint-disable-next-line testing-library/no-node-access -- the wrapper class is a deliberate DOM contract with no semantic query.
|
|
68
74
|
expect(screen.getByText('+2').parentElement).toHaveTextContent('tile 1')
|
|
69
75
|
})
|
|
70
76
|
|
|
@@ -73,7 +79,8 @@ describe('MediaStackGrid', () => {
|
|
|
73
79
|
expect(screen.queryByText(/^\+/)).toBeNull()
|
|
74
80
|
})
|
|
75
81
|
|
|
76
|
-
it('renders activatable buttons with labels and indexes', () => {
|
|
82
|
+
it('renders activatable buttons with labels and indexes', async () => {
|
|
83
|
+
const user = userEvent.setup()
|
|
77
84
|
const onActivate = vi.fn()
|
|
78
85
|
renderWithProviders(
|
|
79
86
|
<MediaStackGrid tiles={tiles(3)} onTileActivate={onActivate} />
|
|
@@ -82,8 +89,8 @@ describe('MediaStackGrid', () => {
|
|
|
82
89
|
expect(buttons).toHaveLength(3)
|
|
83
90
|
expect(buttons[0]).toHaveAttribute('aria-label', 'Open media 1')
|
|
84
91
|
expect(buttons[1]).toHaveAttribute('aria-label', 'Second tile')
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
await user.click(buttons[0])
|
|
93
|
+
await user.click(buttons[2])
|
|
87
94
|
expect(onActivate).toHaveBeenNthCalledWith(1, 0)
|
|
88
95
|
expect(onActivate).toHaveBeenNthCalledWith(2, 2)
|
|
89
96
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { userEvent, renderWithProviders, screen } from '../../../test/utils'
|
|
4
4
|
|
|
5
5
|
import PdfViewer from './PdfViewer'
|
|
6
6
|
|
|
@@ -14,6 +14,7 @@ describe('PdfViewer', () => {
|
|
|
14
14
|
const { container } = renderWithProviders(
|
|
15
15
|
<PdfViewer open onClose={vi.fn()} items={[]} />
|
|
16
16
|
)
|
|
17
|
+
// eslint-disable-next-line testing-library/no-node-access -- the null-rendering DOM contract has no semantic query.
|
|
17
18
|
expect(container.firstChild).toBeNull()
|
|
18
19
|
})
|
|
19
20
|
|
|
@@ -30,7 +31,8 @@ describe('PdfViewer', () => {
|
|
|
30
31
|
)
|
|
31
32
|
})
|
|
32
33
|
|
|
33
|
-
it('uses filename precedence and navigates stacked documents', () => {
|
|
34
|
+
it('uses filename precedence and navigates stacked documents', async () => {
|
|
35
|
+
const user = userEvent.setup()
|
|
34
36
|
renderWithProviders(
|
|
35
37
|
<PdfViewer open onClose={vi.fn()} items={items} initialIndex={1} />
|
|
36
38
|
)
|
|
@@ -39,7 +41,7 @@ describe('PdfViewer', () => {
|
|
|
39
41
|
'second.pdf'
|
|
40
42
|
)
|
|
41
43
|
expect(screen.getByText('2 / 2')).toBeInTheDocument()
|
|
42
|
-
|
|
44
|
+
await user.click(screen.getByRole('button', { name: 'Previous document' }))
|
|
43
45
|
expect(screen.getByTitle('one.pdf')).toBeInTheDocument()
|
|
44
46
|
})
|
|
45
47
|
})
|
|
@@ -2,10 +2,10 @@ import { describe, expect, it, vi } from 'vitest'
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
act,
|
|
5
|
-
fireEvent,
|
|
6
5
|
renderWithProviders,
|
|
7
6
|
screen,
|
|
8
7
|
within,
|
|
8
|
+
userEvent,
|
|
9
9
|
} from '../../../test/utils'
|
|
10
10
|
|
|
11
11
|
import VideoViewer from './VideoViewer'
|
|
@@ -26,6 +26,7 @@ describe('VideoViewer', () => {
|
|
|
26
26
|
const { container } = renderWithProviders(
|
|
27
27
|
<VideoViewer open onClose={vi.fn()} items={[]} />
|
|
28
28
|
)
|
|
29
|
+
// eslint-disable-next-line testing-library/no-node-access -- the null-rendering DOM contract has no semantic query.
|
|
29
30
|
expect(container.firstChild).toBeNull()
|
|
30
31
|
})
|
|
31
32
|
|
|
@@ -33,6 +34,7 @@ describe('VideoViewer', () => {
|
|
|
33
34
|
renderWithProviders(
|
|
34
35
|
<VideoViewer open onClose={vi.fn()} items={[items[0]]} />
|
|
35
36
|
)
|
|
37
|
+
// eslint-disable-next-line testing-library/no-node-access -- media or rendered-markup internals have no user-facing query.
|
|
36
38
|
const video = screen.getByTestId('video-viewer').querySelector('video')
|
|
37
39
|
expect(video).not.toBeNull()
|
|
38
40
|
if (!video) throw new Error('Expected video element')
|
|
@@ -41,20 +43,25 @@ describe('VideoViewer', () => {
|
|
|
41
43
|
expect(video).toHaveAttribute('preload', 'auto')
|
|
42
44
|
expect(video).toHaveAttribute('autoplay')
|
|
43
45
|
expect(video?.muted).toBe(true)
|
|
46
|
+
// eslint-disable-next-line testing-library/no-node-access -- media or rendered-markup internals have no user-facing query.
|
|
44
47
|
expect(video.querySelector('source')).toHaveAttribute('type', 'video/mp4')
|
|
45
48
|
})
|
|
46
49
|
|
|
47
|
-
it('defaults preload and navigates stacked items', () => {
|
|
50
|
+
it('defaults preload and navigates stacked items', async () => {
|
|
51
|
+
const user = userEvent.setup()
|
|
48
52
|
renderWithProviders(<VideoViewer open onClose={vi.fn()} items={items} />)
|
|
49
53
|
expect(screen.getByText('1 / 2')).toBeInTheDocument()
|
|
50
54
|
expect(
|
|
55
|
+
// eslint-disable-next-line testing-library/no-node-access -- media or rendered-markup internals have no user-facing query.
|
|
51
56
|
screen.getByTestId('video-viewer').querySelector('video')
|
|
52
57
|
).toHaveAttribute('preload', 'auto')
|
|
53
|
-
|
|
58
|
+
await user.click(screen.getByRole('button', { name: 'Next video' }))
|
|
54
59
|
expect(
|
|
60
|
+
// eslint-disable-next-line testing-library/no-node-access -- media or rendered-markup internals have no user-facing query.
|
|
55
61
|
screen.getByTestId('video-viewer').querySelector('video')
|
|
56
62
|
).toHaveAttribute('src', items[1].src)
|
|
57
63
|
expect(
|
|
64
|
+
// eslint-disable-next-line testing-library/no-node-access -- media or rendered-markup internals have no user-facing query.
|
|
58
65
|
screen.getByTestId('video-viewer').querySelector('video')
|
|
59
66
|
).toHaveAttribute('preload', 'metadata')
|
|
60
67
|
})
|
|
@@ -85,6 +92,7 @@ describe('VideoViewer', () => {
|
|
|
85
92
|
const { rerender } = renderWithProviders(
|
|
86
93
|
<VideoViewer open onClose={vi.fn()} items={[items[0]]} />
|
|
87
94
|
)
|
|
95
|
+
// eslint-disable-next-line testing-library/no-node-access -- media or rendered-markup internals have no user-facing query.
|
|
88
96
|
const video = screen.getByTestId('video-viewer').querySelector('video')
|
|
89
97
|
if (!video) throw new Error('Expected video element')
|
|
90
98
|
const pause = vi.spyOn(video, 'pause').mockImplementation(() => {})
|
|
@@ -96,6 +104,7 @@ describe('VideoViewer', () => {
|
|
|
96
104
|
// Still there, so the last frame doesn't blink out from under the
|
|
97
105
|
// 150ms backdrop fade — but paused, so nothing plays under it.
|
|
98
106
|
expect(
|
|
107
|
+
// eslint-disable-next-line testing-library/no-node-access -- media or rendered-markup internals have no user-facing query.
|
|
99
108
|
screen.getByTestId('video-viewer').querySelector('video')
|
|
100
109
|
).not.toBeNull()
|
|
101
110
|
expect(pause).toHaveBeenCalled()
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { useLayoutEffect } from 'react'
|
|
2
2
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
userEvent,
|
|
6
|
+
fireEvent,
|
|
7
|
+
renderWithProviders,
|
|
8
|
+
screen,
|
|
9
|
+
} from '../../../test/utils'
|
|
5
10
|
|
|
6
11
|
import { VIEWER_ACTION_CLASS } from './viewerChromeClasses'
|
|
7
12
|
import ViewerShell from './ViewerShell'
|
|
@@ -33,6 +38,7 @@ const restoreDialogMethod = (
|
|
|
33
38
|
|
|
34
39
|
const PreOpenDialog = () => {
|
|
35
40
|
useLayoutEffect(() => {
|
|
41
|
+
// eslint-disable-next-line testing-library/no-node-access -- media or rendered-markup internals have no user-facing query.
|
|
36
42
|
document.querySelector('[data-testid="viewer"]')?.setAttribute('open', '')
|
|
37
43
|
}, [])
|
|
38
44
|
return null
|
|
@@ -201,7 +207,7 @@ describe('ViewerShell', () => {
|
|
|
201
207
|
</ViewerShell>
|
|
202
208
|
)
|
|
203
209
|
).not.toThrow()
|
|
204
|
-
expect(
|
|
210
|
+
expect(previouslyFocused).not.toHaveFocus()
|
|
205
211
|
})
|
|
206
212
|
|
|
207
213
|
it('uses the default accessible label', () => {
|
|
@@ -227,7 +233,8 @@ describe('ViewerShell', () => {
|
|
|
227
233
|
expect(close).not.toHaveBeenCalled()
|
|
228
234
|
})
|
|
229
235
|
|
|
230
|
-
it('closes from the close, native close, and backdrop events', () => {
|
|
236
|
+
it('closes from the close, native close, and backdrop events', async () => {
|
|
237
|
+
const user = userEvent.setup()
|
|
231
238
|
const onClose = vi.fn()
|
|
232
239
|
renderWithProviders(
|
|
233
240
|
<ViewerShell open onClose={onClose} data-testid="viewer">
|
|
@@ -235,12 +242,12 @@ describe('ViewerShell', () => {
|
|
|
235
242
|
</ViewerShell>
|
|
236
243
|
)
|
|
237
244
|
const dialog = screen.getByTestId('viewer')
|
|
238
|
-
|
|
245
|
+
await user.click(screen.getByRole('button', { name: 'Close viewer' }))
|
|
239
246
|
fireEvent(dialog, new Event('close'))
|
|
240
|
-
|
|
247
|
+
await user.click(dialog)
|
|
241
248
|
expect(onClose).toHaveBeenCalledTimes(3)
|
|
242
249
|
onClose.mockClear()
|
|
243
|
-
|
|
250
|
+
await user.click(screen.getByText('content'))
|
|
244
251
|
expect(onClose).not.toHaveBeenCalled()
|
|
245
252
|
})
|
|
246
253
|
})
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
userEvent,
|
|
5
|
+
fireEvent,
|
|
6
|
+
renderWithProviders,
|
|
7
|
+
screen,
|
|
8
|
+
} from '../../../test/utils'
|
|
4
9
|
|
|
5
10
|
import { useCarousel } from './useCarousel'
|
|
6
11
|
|
|
@@ -28,14 +33,15 @@ const Harness = ({
|
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
describe('useCarousel', () => {
|
|
31
|
-
it('clamps initial index, wraps, and resets on reopen', () => {
|
|
36
|
+
it('clamps initial index, wraps, and resets on reopen', async () => {
|
|
37
|
+
const user = userEvent.setup()
|
|
32
38
|
const { rerender } = renderWithProviders(
|
|
33
39
|
<Harness open length={3} initialIndex={99} />
|
|
34
40
|
)
|
|
35
41
|
expect(screen.getByTestId('index')).toHaveTextContent('2')
|
|
36
|
-
|
|
42
|
+
await user.click(screen.getByRole('button', { name: 'Next' }))
|
|
37
43
|
expect(screen.getByTestId('index')).toHaveTextContent('0')
|
|
38
|
-
|
|
44
|
+
await user.click(screen.getByRole('button', { name: 'Prev' }))
|
|
39
45
|
expect(screen.getByTestId('index')).toHaveTextContent('2')
|
|
40
46
|
rerender(<Harness open={false} length={3} initialIndex={1} />)
|
|
41
47
|
rerender(<Harness open length={3} initialIndex={1} />)
|
|
@@ -23,11 +23,15 @@ describe('MessageBubble', () => {
|
|
|
23
23
|
<MessageBubble.Sent text="tailed" />
|
|
24
24
|
)
|
|
25
25
|
// The bubble box wraps the text; its last child is the tail <span>.
|
|
26
|
+
// eslint-disable-next-line testing-library/no-node-access -- the wrapper class is a deliberate DOM contract with no semantic query.
|
|
26
27
|
const bubble = screen.getByText('tailed').parentElement as HTMLElement
|
|
28
|
+
// eslint-disable-next-line testing-library/no-node-access -- media or rendered-markup internals have no user-facing query.
|
|
27
29
|
expect(bubble.querySelector('span[aria-hidden="true"]')).not.toBeNull()
|
|
28
30
|
|
|
29
31
|
rerender(<MessageBubble.Sent text="tailed" tail={false} />)
|
|
32
|
+
// eslint-disable-next-line testing-library/no-node-access -- the wrapper class is a deliberate DOM contract with no semantic query.
|
|
30
33
|
const bubbleNoTail = screen.getByText('tailed').parentElement as HTMLElement
|
|
34
|
+
// eslint-disable-next-line testing-library/no-node-access -- media or rendered-markup internals have no user-facing query.
|
|
31
35
|
expect(bubbleNoTail.querySelector('span[aria-hidden="true"]')).toBeNull()
|
|
32
36
|
})
|
|
33
37
|
|
|
@@ -47,12 +51,14 @@ describe('MessageBubble', () => {
|
|
|
47
51
|
|
|
48
52
|
it('applies the sent and received surface treatments from theme vars', () => {
|
|
49
53
|
const { rerender } = renderWithProviders(<MessageBubble.Sent text="mine" />)
|
|
54
|
+
// eslint-disable-next-line testing-library/no-node-access -- the wrapper class is a deliberate DOM contract with no semantic query.
|
|
50
55
|
const sent = screen.getByText('mine').parentElement as HTMLElement
|
|
51
56
|
expect(sent.style.backgroundColor).toBe(
|
|
52
57
|
'var(--str-chat__own-message-bubble-background-color, #1e2330)'
|
|
53
58
|
)
|
|
54
59
|
|
|
55
60
|
rerender(<MessageBubble.Received text="theirs" />)
|
|
61
|
+
// eslint-disable-next-line testing-library/no-node-access -- the wrapper class is a deliberate DOM contract with no semantic query.
|
|
56
62
|
const received = screen.getByText('theirs').parentElement as HTMLElement
|
|
57
63
|
expect(received.style.backgroundColor).toBe(
|
|
58
64
|
'var(--str-chat__message-bubble-background-color, #f1f0ee)'
|
|
@@ -61,6 +67,7 @@ describe('MessageBubble', () => {
|
|
|
61
67
|
|
|
62
68
|
it('masks message content from session replay by default', () => {
|
|
63
69
|
renderWithProviders(<MessageBubble.Received header="Brie" text="secret" />)
|
|
70
|
+
// eslint-disable-next-line testing-library/no-node-access -- the wrapper class is a deliberate DOM contract with no semantic query.
|
|
64
71
|
const bubble = screen.getByText('secret').parentElement as HTMLElement
|
|
65
72
|
expect(bubble.getAttribute('data-dd-privacy')).toBe('mask')
|
|
66
73
|
})
|
|
@@ -69,6 +76,7 @@ describe('MessageBubble', () => {
|
|
|
69
76
|
renderWithProviders(
|
|
70
77
|
<MessageBubble.Sent text="wide" className="w-fit max-w-[520px]" />
|
|
71
78
|
)
|
|
79
|
+
// eslint-disable-next-line testing-library/no-node-access -- the wrapper class is a deliberate DOM contract with no semantic query.
|
|
72
80
|
const bubble = screen.getByText('wide').parentElement as HTMLElement
|
|
73
81
|
expect(bubble.classList.contains('w-fit')).toBe(true)
|
|
74
82
|
})
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { describe, expect, it, vi } from 'vitest'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
userEvent,
|
|
6
|
+
renderWithProviders,
|
|
7
|
+
screen,
|
|
8
|
+
fireEvent,
|
|
9
|
+
} from '../../test/utils'
|
|
5
10
|
|
|
6
11
|
import RichCard from '.'
|
|
7
12
|
|
|
@@ -41,6 +46,7 @@ describe('RichCard', () => {
|
|
|
41
46
|
expect(card).toHaveTextContent('👋 Say hello as people arrive')
|
|
42
47
|
expect(card).toHaveTextContent('✨ Spark conversations')
|
|
43
48
|
expect(card).toHaveTextContent('💬 Add automated FAQs')
|
|
49
|
+
// eslint-disable-next-line testing-library/no-node-access -- media or rendered-markup internals have no user-facing query.
|
|
44
50
|
expect(card.querySelectorAll('br')).toHaveLength(2)
|
|
45
51
|
})
|
|
46
52
|
|
|
@@ -57,6 +63,7 @@ describe('RichCard', () => {
|
|
|
57
63
|
)
|
|
58
64
|
|
|
59
65
|
const card = await screen.findByTestId('rich-card')
|
|
66
|
+
// eslint-disable-next-line testing-library/no-node-access -- media or rendered-markup internals have no user-facing query.
|
|
60
67
|
expect(card.querySelectorAll('br')).toHaveLength(0)
|
|
61
68
|
expect(screen.getByText(/Say hello as people arrive/)).toHaveClass(
|
|
62
69
|
'truncate'
|
|
@@ -197,6 +204,7 @@ describe('RichCard', () => {
|
|
|
197
204
|
})
|
|
198
205
|
|
|
199
206
|
it('renders a url action as an external anchor, others as buttons', async () => {
|
|
207
|
+
const user = userEvent.setup()
|
|
200
208
|
const onClick = vi.fn()
|
|
201
209
|
renderWithProviders(
|
|
202
210
|
<RichCard.Received
|
|
@@ -212,7 +220,7 @@ describe('RichCard', () => {
|
|
|
212
220
|
expect(link).toHaveAttribute('href', 'https://tr.ee/x')
|
|
213
221
|
expect(link).toHaveAttribute('target', '_blank')
|
|
214
222
|
|
|
215
|
-
|
|
223
|
+
await user.click(screen.getByRole('button', { name: 'Set up' }))
|
|
216
224
|
expect(onClick).toHaveBeenCalledTimes(1)
|
|
217
225
|
})
|
|
218
226
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { SpinnerGapIcon } from '@phosphor-icons/react'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { renderWithProviders } from '../../test/utils'
|
|
5
|
+
|
|
6
|
+
import Spinner from './Spinner'
|
|
7
|
+
|
|
8
|
+
const getSvg = (container: HTMLElement) => {
|
|
9
|
+
// eslint-disable-next-line testing-library/no-node-access -- Spinner is tested through its rendered SVG.
|
|
10
|
+
return container.querySelector('svg')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe('Spinner', () => {
|
|
14
|
+
it('renders the default size and spinning class', () => {
|
|
15
|
+
const { container } = renderWithProviders(<Spinner />)
|
|
16
|
+
const svg = getSvg(container)
|
|
17
|
+
|
|
18
|
+
// eslint-disable-next-line local/no-style-only-assertion -- Spinner's contract is its class output.
|
|
19
|
+
expect(svg).toHaveClass('animate-spin', 'size-4')
|
|
20
|
+
// eslint-disable-next-line local/no-style-only-assertion -- Spinner's contract is its class output.
|
|
21
|
+
expect(svg).not.toHaveClass('text-white/80')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it.each([
|
|
25
|
+
['md', 'size-5'],
|
|
26
|
+
['lg', 'size-8'],
|
|
27
|
+
] as const)('supports %s size', (size, sizeClass) => {
|
|
28
|
+
const { container } = renderWithProviders(<Spinner size={size} />)
|
|
29
|
+
|
|
30
|
+
expect(getSvg(container)).toHaveClass(sizeClass)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('supports the on-media tone', () => {
|
|
34
|
+
const { container, rerender } = renderWithProviders(
|
|
35
|
+
<Spinner tone="on-media" />
|
|
36
|
+
)
|
|
37
|
+
// eslint-disable-next-line local/no-style-only-assertion -- Spinner's contract is its class output.
|
|
38
|
+
expect(getSvg(container)).toHaveClass('text-white/80')
|
|
39
|
+
|
|
40
|
+
rerender(<Spinner />)
|
|
41
|
+
// eslint-disable-next-line local/no-style-only-assertion -- Spinner's contract is its class output.
|
|
42
|
+
expect(getSvg(container)).not.toHaveClass('text-white/80')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('renders the supplied icon', () => {
|
|
46
|
+
const { container: defaultContainer } = renderWithProviders(<Spinner />)
|
|
47
|
+
const { container: suppliedContainer } = renderWithProviders(
|
|
48
|
+
<Spinner icon={SpinnerGapIcon} />
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
expect(getSvg(suppliedContainer)?.innerHTML).not.toBe(
|
|
52
|
+
getSvg(defaultContainer)?.innerHTML
|
|
53
|
+
)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('only renders aria-hidden when passed', () => {
|
|
57
|
+
const { container, rerender } = renderWithProviders(<Spinner />)
|
|
58
|
+
expect(getSvg(container)).not.toHaveAttribute('aria-hidden')
|
|
59
|
+
|
|
60
|
+
rerender(<Spinner aria-hidden />)
|
|
61
|
+
expect(getSvg(container)).toHaveAttribute('aria-hidden', 'true')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('merges a custom className with its built-in classes', () => {
|
|
65
|
+
const { container } = renderWithProviders(
|
|
66
|
+
<Spinner className="custom-spinner" />
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
expect(getSvg(container)).toHaveClass(
|
|
70
|
+
'animate-spin',
|
|
71
|
+
'size-4',
|
|
72
|
+
'custom-spinner'
|
|
73
|
+
)
|
|
74
|
+
})
|
|
75
|
+
})
|