Skip to content
304 changes: 177 additions & 127 deletions src/packages/imagepreview/__test__/imagepreview.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,148 +1,198 @@
import * as React from 'react'
import { render, waitFor, act } from '@testing-library/react'
import { render, screen, fireEvent } from '@testing-library/react'
import '@testing-library/jest-dom'
import { ImagePreview } from '../imagepreview'
import { triggerDrag } from '@/utils/test/event'

const images = [
{
src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/18629/34/3378/144318/5c263f64Ef0e2bff0/0d650e0aa2e852ee.jpg',
},
{
src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/26597/30/4870/174583/5c35c5d2Ed55eedc6/50e27870c25e7a82.png',
},
{
src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/9542/17/12873/201687/5c3c4362Ea9eb757d/60026b40a9d60d85.jpg',
},
{
src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/30042/36/427/82951/5c3bfdabE3faf2f66/9adca782661c988c.jpg',
},
]

const videos = [
{
source: {
src: 'https://storage.jd.com/about/big-final.mp4?Expires=3730193075&AccessKey=3LoYX1dQWa6ZXzQl&Signature=ViMFjz%2BOkBxS%2FY1rjtUVqbopbJI%3D',
type: 'video/mp4',
describe('ImagePreview Component', () => {
const images = [
{
src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/18629/34/3378/144318/5c263f64Ef0e2bff0/0d650e0aa2e852ee.jpg',
},
options: {
muted: true,
controls: true,
{
src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/26597/30/4870/174583/5c35c5d2Ed55eedc6/50e27870c25e7a82.png',
},
},
{
source: {
src: 'https://storage.jd.com/about/big-final.mp4?Expires=3730193075&AccessKey=3LoYX1dQWa6ZXzQl&Signature=ViMFjz%2BOkBxS%2FY1rjtUVqbopbJI%3D',
type: 'video/mp4',
{
src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/9542/17/12873/201687/5c3c4362Ea9eb757d/60026b40a9d60d85.jpg',
},
options: {
muted: true,
controls: true,
{
src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/30042/36/427/82951/5c3bfdabE3faf2f66/9adca782661c988c.jpg',
},
},
]

function sleep(delay = 0): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, delay)
})
}
]

test('basic usage test', () => {
const { container } = render(<ImagePreview images={images} visible />)
const videos = [
{
source: {
src: 'https://storage.jd.com/about/big-final.mp4?Expires=3730193075&AccessKey=3LoYX1dQWa6ZXzQl&Signature=ViMFjz%2BOkBxS%2FY1rjtUVqbopbJI%3D',
type: 'video/mp4',
},
options: {
muted: true,
controls: true,
},
},
{
source: {
src: 'https://storage.jd.com/about/big-final.mp4?Expires=3730193075&AccessKey=3LoYX1dQWa6ZXzQl&Signature=ViMFjz%2BOkBxS%2FY1rjtUVqbopbJI%3D',
type: 'video/mp4',
},
options: {
muted: true,
controls: true,
},
},
]

const mockOnChange = vi.fn()
const mockOnClose = vi.fn()

const setup = (props = {}) => {
render(
<ImagePreview
images={images}
videos={videos}
visible
closeIcon
defaultValue={0}
onChange={mockOnChange}
onClose={mockOnClose}
{...props}
/>
)
}

const element = container.querySelector(
'.nut-imagepreview-pop'
) as HTMLElement
expect(element.style.display).toEqual('')
})
afterEach(() => {
vi.clearAllMocks()
})

test('test autoPlay', async () => {
let _container: any
act(() => {
test('renders correctly when visible', async () => {
const { container } = render(
<ImagePreview images={images} visible autoPlay={1000} />
<ImagePreview
images={images}
videos={videos}
visible
defaultValue={0}
onChange={mockOnChange}
onClose={mockOnClose}
/>
)
expect(screen.getByText('1/6')).toBeInTheDocument() // Assuming pagination is shown
expect((await container).getElementsByTagName('img')[0]).toHaveAttribute(
'src',
'//m.360buyimg.com/mobilecms/s750x366_jfs/t1/18629/34/3378/144318/5c263f64Ef0e2bff0/0d650e0aa2e852ee.jpg'
)
_container = container
// 验证视频元素
const videoElements = container.getElementsByTagName('video')
expect(videoElements.length).toBe(2)
// 验证轮播容器
expect(container.querySelector('.nut-swiper')).toBeInTheDocument()
})

const element = _container.querySelector(
'.nut-imagepreview-pop .nut-imagepreview-index'
) as HTMLElement
expect(element).toHaveTextContent('1')

await waitFor(
async () => {
await sleep(1100)
expect(element).toHaveTextContent('2')
},
{
timeout: 2000,
}
)
})

test('init page No.', async () => {
const { container } = render(
<ImagePreview images={images} visible defaultValue={3} />
)

const element = container.querySelector(
'.nut-imagepreview-pop .nut-imagepreview-index'
) as HTMLElement
expect(element).toHaveTextContent('3/4')
})

test('customize indicator and color', async () => {
const { container } = render(
<ImagePreview images={images} visible indicator indicatorColor="red" />
)

const swiperIndicator = container.querySelector('.nut-imagepreview-swiper')
expect(swiperIndicator).toHaveAttribute(
'style',
'--nutui-indicator-color: red;'
)
})

test('video surported in H5 env', async () => {
const { container } = render(
<ImagePreview images={images} videos={videos} visible />
)

const nutVideoPlayer = container.querySelector('.nut-video-player')
expect(nutVideoPlayer).toBeInTheDocument()
})
test('calls onClose when close icon is clicked', async () => {
const { container } = render(
<ImagePreview images={images} visible closeIcon onClose={mockOnClose} />
)
const closeIcon = container.querySelector('.nut-imagepreview-close')
expect(closeIcon).toBeInTheDocument()
expect(closeIcon?.classList).toContain('top-right')
fireEvent.click(closeIcon as Element)
expect(mockOnClose).toHaveBeenCalledTimes(1)
})

test('closeIcon = true', async () => {
const { container } = render(
<ImagePreview images={images} videos={videos} visible closeIcon />
)
test('closes on content click if closeOnContentClick is true', async () => {
const { container } = render(
<ImagePreview
images={images}
visible
closeIcon
closeOnContentClick
onClose={mockOnClose}
/>
)
const imageElement = container.querySelector('.nut-image-default')
fireEvent.click(imageElement as Element)
expect(mockOnClose).toHaveBeenCalledTimes(1)
})

const closeIcon = container.querySelector('.nut-imagepreview-close')
expect(closeIcon).toBeInTheDocument()
expect(closeIcon?.classList).toContain('top-right')
})
test('init page No.', async () => {
const { container } = render(
<ImagePreview images={images} visible defaultValue={3} />
)
const element = container.querySelector(
'.nut-imagepreview-pop .nut-imagepreview-index'
) as HTMLElement
expect(element).toHaveTextContent('3/4')
})

test('custom closeIcon', async () => {
const { container } = render(
<ImagePreview images={images} videos={videos} visible closeIcon="close" />
)
test('does not close on content click if closeOnContentClick is false', () => {
const { container } = render(
<ImagePreview images={images} visible closeOnContentClick={false} />
)
const imageElement = container.querySelector('.nut-image-default')
fireEvent.click(imageElement as Element)
expect(mockOnClose).toHaveBeenCalledTimes(0)
})

const closeIcon = container.querySelector('.nut-imagepreview-close')
expect(closeIcon?.innerHTML).toContain('close')
})
test('handles zooming in and out on touch events', () => {
const { container } = render(<ImagePreview images={images} visible />)
const swiperIndicator = container.querySelector(
'.nut-imagepreview'
) as Element

// 测试放大
// Simulate touch start for zoom in
fireEvent.touchStart(swiperIndicator, {
touches: [
{ pageX: 100, pageY: 100 },
{ pageX: 200, pageY: 200 },
],
})

// Simulate touch move for zooming
fireEvent.touchMove(swiperIndicator, {
touches: [
{ pageX: 100, pageY: 100 },
{ pageX: 300, pageY: 300 },
],
})

// Verify that scale function has been called or scale state has changed
// Since we don't expose the scale, we may need to check the style if set
expect((swiperIndicator as HTMLElement).style.transform).toContain('scale(')
const transformAfterZoomIn = (swiperIndicator as HTMLElement).style
.transform
expect(transformAfterZoomIn).toMatch(/scale\([\d.]+\)/)
// 测试缩小
fireEvent.touchStart(swiperIndicator, {
touches: [
{ pageX: 300, pageY: 300 },
{ pageX: 400, pageY: 400 },
],
})
fireEvent.touchMove(swiperIndicator, {
touches: [
{ pageX: 300, pageY: 300 },
{ pageX: 350, pageY: 350 },
],
})

const transformAfterZoomOut = (swiperIndicator as HTMLElement).style
.transform
expect(transformAfterZoomOut).toMatch(/scale\(1\)/)

// 测试触摸结束
fireEvent.touchEnd(swiperIndicator)
})

test('closeIconPosition', async () => {
const { container } = render(
<ImagePreview
images={images}
videos={videos}
visible
closeIcon
closeIconPosition="bottom"
/>
)

const closeIcon = container.querySelector('.nut-imagepreview-close')
expect(closeIcon?.classList).toContain('bottom')
test('autoPlay', async () => {
const { container } = render(
<ImagePreview images={images} videos={videos} visible autoPlay={2000} />
)
const swiper = container.querySelectorAll('.nut-swiper')[0]
const swiperItem = container.querySelector('.nut-swiper-slide')
triggerDrag(swiper, 220, 0)
expect(swiperItem).toHaveStyle({
transform: 'translate3d(100%,0,0)',
})
})
Comment on lines +187 to +197
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

完善自动播放测试

自动播放测试需要验证定时器和切换效果,当前实现可能不够稳定。建议使用 jest.useFakeTimers() 来控制时间。

 test('autoPlay', async () => {
+  jest.useFakeTimers()
   const { container } = render(
     <ImagePreview images={images} videos={videos} visible autoPlay={2000} />
   )
   const swiper = container.querySelectorAll('.nut-swiper')[0]
   const swiperItem = container.querySelector('.nut-swiper-slide')
-  triggerDrag(swiper, 220, 0)
-  expect(swiperItem).toHaveStyle({
-    transform: 'translate3d(100%,0,0)',
-  })
+  
+  // 验证初始状态
+  expect(screen.getByText('1/6')).toBeInTheDocument()
+  
+  // 等待自动播放
+  jest.advanceTimersByTime(2000)
+  
+  // 验证是否切换到下一张
+  expect(screen.getByText('2/6')).toBeInTheDocument()
+  
+  jest.useRealTimers()
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('autoPlay', async () => {
const { container } = render(
<ImagePreview images={images} videos={videos} visible autoPlay={2000} />
)
const swiper = container.querySelectorAll('.nut-swiper')[0]
const swiperItem = container.querySelector('.nut-swiper-slide')
triggerDrag(swiper, 220, 0)
expect(swiperItem).toHaveStyle({
transform: 'translate3d(100%,0,0)',
})
})
test('autoPlay', async () => {
jest.useFakeTimers()
const { container } = render(
<ImagePreview images={images} videos={videos} visible autoPlay={2000} />
)
const swiper = container.querySelectorAll('.nut-swiper')[0]
const swiperItem = container.querySelector('.nut-swiper-slide')
// 验证初始状态
expect(screen.getByText('1/6')).toBeInTheDocument()
// 等待自动播放
jest.advanceTimersByTime(2000)
// 验证是否切换到下一张
expect(screen.getByText('2/6')).toBeInTheDocument()
jest.useRealTimers()
})

})
3 changes: 2 additions & 1 deletion src/packages/imagepreview/imagepreview.scss
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,12 @@
}

&-pop {
width: 100%;
height: 100%;
max-width: 100% !important;
background: transparent !important;
display: flex;
align-items: center;
width: 100%;
}

&-swiper {
Expand Down
Loading
Loading