Skip to content

Commit 0ed86df

Browse files
authored
fix(ImagePreview): 优化代码,fix 因为popup最大宽度导致的问题 (#2939)
* fix: 优化imagepreview 代码 * feat: 给taro版本增加缩放功能,优化代码 * test: fixed * test: fix * test: fix * test: fix * fix: 简写 * fix: cr * fix: cr * fix: 性能优化 * fix: 性能优化
1 parent e049b0e commit 0ed86df

File tree

8 files changed

+319
-371
lines changed

8 files changed

+319
-371
lines changed
Lines changed: 177 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,148 +1,198 @@
11
import * as React from 'react'
2-
import { render, waitFor, act } from '@testing-library/react'
2+
import { render, screen, fireEvent } from '@testing-library/react'
33
import '@testing-library/jest-dom'
44
import { ImagePreview } from '../imagepreview'
5+
import { triggerDrag } from '@/utils/test/event'
56

6-
const images = [
7-
{
8-
src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/18629/34/3378/144318/5c263f64Ef0e2bff0/0d650e0aa2e852ee.jpg',
9-
},
10-
{
11-
src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/26597/30/4870/174583/5c35c5d2Ed55eedc6/50e27870c25e7a82.png',
12-
},
13-
{
14-
src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/9542/17/12873/201687/5c3c4362Ea9eb757d/60026b40a9d60d85.jpg',
15-
},
16-
{
17-
src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/30042/36/427/82951/5c3bfdabE3faf2f66/9adca782661c988c.jpg',
18-
},
19-
]
20-
21-
const videos = [
22-
{
23-
source: {
24-
src: 'https://storage.jd.com/about/big-final.mp4?Expires=3730193075&AccessKey=3LoYX1dQWa6ZXzQl&Signature=ViMFjz%2BOkBxS%2FY1rjtUVqbopbJI%3D',
25-
type: 'video/mp4',
7+
describe('ImagePreview Component', () => {
8+
const images = [
9+
{
10+
src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/18629/34/3378/144318/5c263f64Ef0e2bff0/0d650e0aa2e852ee.jpg',
2611
},
27-
options: {
28-
muted: true,
29-
controls: true,
12+
{
13+
src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/26597/30/4870/174583/5c35c5d2Ed55eedc6/50e27870c25e7a82.png',
3014
},
31-
},
32-
{
33-
source: {
34-
src: 'https://storage.jd.com/about/big-final.mp4?Expires=3730193075&AccessKey=3LoYX1dQWa6ZXzQl&Signature=ViMFjz%2BOkBxS%2FY1rjtUVqbopbJI%3D',
35-
type: 'video/mp4',
15+
{
16+
src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/9542/17/12873/201687/5c3c4362Ea9eb757d/60026b40a9d60d85.jpg',
3617
},
37-
options: {
38-
muted: true,
39-
controls: true,
18+
{
19+
src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/30042/36/427/82951/5c3bfdabE3faf2f66/9adca782661c988c.jpg',
4020
},
41-
},
42-
]
43-
44-
function sleep(delay = 0): Promise<void> {
45-
return new Promise((resolve) => {
46-
setTimeout(resolve, delay)
47-
})
48-
}
21+
]
4922

50-
test('basic usage test', () => {
51-
const { container } = render(<ImagePreview images={images} visible />)
23+
const videos = [
24+
{
25+
source: {
26+
src: 'https://storage.jd.com/about/big-final.mp4?Expires=3730193075&AccessKey=3LoYX1dQWa6ZXzQl&Signature=ViMFjz%2BOkBxS%2FY1rjtUVqbopbJI%3D',
27+
type: 'video/mp4',
28+
},
29+
options: {
30+
muted: true,
31+
controls: true,
32+
},
33+
},
34+
{
35+
source: {
36+
src: 'https://storage.jd.com/about/big-final.mp4?Expires=3730193075&AccessKey=3LoYX1dQWa6ZXzQl&Signature=ViMFjz%2BOkBxS%2FY1rjtUVqbopbJI%3D',
37+
type: 'video/mp4',
38+
},
39+
options: {
40+
muted: true,
41+
controls: true,
42+
},
43+
},
44+
]
45+
46+
const mockOnChange = vi.fn()
47+
const mockOnClose = vi.fn()
48+
49+
const setup = (props = {}) => {
50+
render(
51+
<ImagePreview
52+
images={images}
53+
videos={videos}
54+
visible
55+
closeIcon
56+
defaultValue={0}
57+
onChange={mockOnChange}
58+
onClose={mockOnClose}
59+
{...props}
60+
/>
61+
)
62+
}
5263

53-
const element = container.querySelector(
54-
'.nut-imagepreview-pop'
55-
) as HTMLElement
56-
expect(element.style.display).toEqual('')
57-
})
64+
afterEach(() => {
65+
vi.clearAllMocks()
66+
})
5867

59-
test('test autoPlay', async () => {
60-
let _container: any
61-
act(() => {
68+
test('renders correctly when visible', async () => {
6269
const { container } = render(
63-
<ImagePreview images={images} visible autoPlay={1000} />
70+
<ImagePreview
71+
images={images}
72+
videos={videos}
73+
visible
74+
defaultValue={0}
75+
onChange={mockOnChange}
76+
onClose={mockOnClose}
77+
/>
78+
)
79+
expect(screen.getByText('1/6')).toBeInTheDocument() // Assuming pagination is shown
80+
expect((await container).getElementsByTagName('img')[0]).toHaveAttribute(
81+
'src',
82+
'//m.360buyimg.com/mobilecms/s750x366_jfs/t1/18629/34/3378/144318/5c263f64Ef0e2bff0/0d650e0aa2e852ee.jpg'
6483
)
65-
_container = container
84+
// 验证视频元素
85+
const videoElements = container.getElementsByTagName('video')
86+
expect(videoElements.length).toBe(2)
87+
// 验证轮播容器
88+
expect(container.querySelector('.nut-swiper')).toBeInTheDocument()
6689
})
6790

68-
const element = _container.querySelector(
69-
'.nut-imagepreview-pop .nut-imagepreview-index'
70-
) as HTMLElement
71-
expect(element).toHaveTextContent('1')
72-
73-
await waitFor(
74-
async () => {
75-
await sleep(1100)
76-
expect(element).toHaveTextContent('2')
77-
},
78-
{
79-
timeout: 2000,
80-
}
81-
)
82-
})
83-
84-
test('init page No.', async () => {
85-
const { container } = render(
86-
<ImagePreview images={images} visible defaultValue={3} />
87-
)
88-
89-
const element = container.querySelector(
90-
'.nut-imagepreview-pop .nut-imagepreview-index'
91-
) as HTMLElement
92-
expect(element).toHaveTextContent('3/4')
93-
})
94-
95-
test('customize indicator and color', async () => {
96-
const { container } = render(
97-
<ImagePreview images={images} visible indicator indicatorColor="red" />
98-
)
99-
100-
const swiperIndicator = container.querySelector('.nut-imagepreview-swiper')
101-
expect(swiperIndicator).toHaveAttribute(
102-
'style',
103-
'--nutui-indicator-color: red;'
104-
)
105-
})
106-
107-
test('video surported in H5 env', async () => {
108-
const { container } = render(
109-
<ImagePreview images={images} videos={videos} visible />
110-
)
111-
112-
const nutVideoPlayer = container.querySelector('.nut-video-player')
113-
expect(nutVideoPlayer).toBeInTheDocument()
114-
})
91+
test('calls onClose when close icon is clicked', async () => {
92+
const { container } = render(
93+
<ImagePreview images={images} visible closeIcon onClose={mockOnClose} />
94+
)
95+
const closeIcon = container.querySelector('.nut-imagepreview-close')
96+
expect(closeIcon).toBeInTheDocument()
97+
expect(closeIcon?.classList).toContain('top-right')
98+
fireEvent.click(closeIcon as Element)
99+
expect(mockOnClose).toHaveBeenCalledTimes(1)
100+
})
115101

116-
test('closeIcon = true', async () => {
117-
const { container } = render(
118-
<ImagePreview images={images} videos={videos} visible closeIcon />
119-
)
102+
test('closes on content click if closeOnContentClick is true', async () => {
103+
const { container } = render(
104+
<ImagePreview
105+
images={images}
106+
visible
107+
closeIcon
108+
closeOnContentClick
109+
onClose={mockOnClose}
110+
/>
111+
)
112+
const imageElement = container.querySelector('.nut-image-default')
113+
fireEvent.click(imageElement as Element)
114+
expect(mockOnClose).toHaveBeenCalledTimes(1)
115+
})
120116

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

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

131-
const closeIcon = container.querySelector('.nut-imagepreview-close')
132-
expect(closeIcon?.innerHTML).toContain('close')
133-
})
136+
test('handles zooming in and out on touch events', () => {
137+
const { container } = render(<ImagePreview images={images} visible />)
138+
const swiperIndicator = container.querySelector(
139+
'.nut-imagepreview'
140+
) as Element
141+
142+
// 测试放大
143+
// Simulate touch start for zoom in
144+
fireEvent.touchStart(swiperIndicator, {
145+
touches: [
146+
{ pageX: 100, pageY: 100 },
147+
{ pageX: 200, pageY: 200 },
148+
],
149+
})
150+
151+
// Simulate touch move for zooming
152+
fireEvent.touchMove(swiperIndicator, {
153+
touches: [
154+
{ pageX: 100, pageY: 100 },
155+
{ pageX: 300, pageY: 300 },
156+
],
157+
})
158+
159+
// Verify that scale function has been called or scale state has changed
160+
// Since we don't expose the scale, we may need to check the style if set
161+
expect((swiperIndicator as HTMLElement).style.transform).toContain('scale(')
162+
const transformAfterZoomIn = (swiperIndicator as HTMLElement).style
163+
.transform
164+
expect(transformAfterZoomIn).toMatch(/scale\([\d.]+\)/)
165+
// 测试缩小
166+
fireEvent.touchStart(swiperIndicator, {
167+
touches: [
168+
{ pageX: 300, pageY: 300 },
169+
{ pageX: 400, pageY: 400 },
170+
],
171+
})
172+
fireEvent.touchMove(swiperIndicator, {
173+
touches: [
174+
{ pageX: 300, pageY: 300 },
175+
{ pageX: 350, pageY: 350 },
176+
],
177+
})
178+
179+
const transformAfterZoomOut = (swiperIndicator as HTMLElement).style
180+
.transform
181+
expect(transformAfterZoomOut).toMatch(/scale\(1\)/)
182+
183+
// 测试触摸结束
184+
fireEvent.touchEnd(swiperIndicator)
185+
})
134186

135-
test('closeIconPosition', async () => {
136-
const { container } = render(
137-
<ImagePreview
138-
images={images}
139-
videos={videos}
140-
visible
141-
closeIcon
142-
closeIconPosition="bottom"
143-
/>
144-
)
145-
146-
const closeIcon = container.querySelector('.nut-imagepreview-close')
147-
expect(closeIcon?.classList).toContain('bottom')
187+
test('autoPlay', async () => {
188+
const { container } = render(
189+
<ImagePreview images={images} videos={videos} visible autoPlay={2000} />
190+
)
191+
const swiper = container.querySelectorAll('.nut-swiper')[0]
192+
const swiperItem = container.querySelector('.nut-swiper-slide')
193+
triggerDrag(swiper, 220, 0)
194+
expect(swiperItem).toHaveStyle({
195+
transform: 'translate3d(100%,0,0)',
196+
})
197+
})
148198
})

src/packages/imagepreview/imagepreview.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,12 @@
6262
}
6363

6464
&-pop {
65+
width: 100%;
6566
height: 100%;
67+
max-width: 100% !important;
6668
background: transparent !important;
6769
display: flex;
6870
align-items: center;
69-
width: 100%;
7071
}
7172

7273
&-swiper {

0 commit comments

Comments
 (0)