Skip to content

Commit df4884a

Browse files
Passing-of-A-Dream刘欢
authored andcommitted
fix(Avatar): 修复空src时未正确显示fallback (ant-design#6935)
* fix: 修复在next.js环境下的报错 * refactor(deps): 从 ahooks 的 useIsomorphicLayoutEffect 迁移到 rc-util 的 useLayoutEffect * fix(Avatar): 修复空src时未正确显示fallback * refactor: 修复变量名以提高代码可读性 * test: 补充src空值测试用例 * fix(Avatar): 增加对空src的处理,确保正确显示fallback内容 * feat(Avatar): 增加onClick事件处理,支持点击头像和fallback内容 * fix: 由Image组件处理fallback,职责划分 * fix: 增加空值判断 * test: 修正单测错误 * fix: 优化渲染逻辑,合并失败和空src的处理
1 parent 7158747 commit df4884a

File tree

5 files changed

+68
-17
lines changed

5 files changed

+68
-17
lines changed

src/components/avatar/avatar.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
import React from 'react'
21
import type { FC, ReactNode } from 'react'
2+
import React from 'react'
33
import { NativeProps, withNativeProps } from '../../utils/native-props'
44
import { mergeProps } from '../../utils/with-default-props'
5-
import { Fallback } from './fallback'
65
import Image, { ImageProps } from '../image'
6+
import { Fallback } from './fallback'
77

88
const classPrefix = 'adm-avatar'
99

1010
export type AvatarProps = {
1111
src: string
1212
fallback?: ReactNode
1313
fit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down'
14-
} & Pick<ImageProps, 'alt' | 'lazy' | 'onClick' | 'onError' | 'onLoad'> &
14+
onClick?: (
15+
event: React.MouseEvent<HTMLDivElement | HTMLImageElement, Event>
16+
) => void
17+
} & Pick<ImageProps, 'alt' | 'lazy' | 'onError' | 'onLoad'> &
1518
NativeProps<'--size' | '--border-radius'>
1619

1720
const defaultProps = {
@@ -21,11 +24,13 @@ const defaultProps = {
2124

2225
export const Avatar: FC<AvatarProps> = p => {
2326
const props = mergeProps(defaultProps, p)
27+
const mergedSrc = props.src?.trim() || undefined
28+
2429
return withNativeProps(
2530
props,
2631
<Image
2732
className={classPrefix}
28-
src={props.src}
33+
src={mergedSrc}
2934
fallback={props.fallback}
3035
placeholder={props.fallback}
3136
alt={props.alt}

src/components/avatar/tests/avatar.test.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,18 @@ describe('Avatar', () => {
1414
render(<Avatar src='/404' />)
1515
expect(document.querySelectorAll('.adm-avatar-fallback')[0]).toBeVisible()
1616
})
17+
18+
test('empty src should show fallback', () => {
19+
render(<Avatar src='' />)
20+
const avatar = document.querySelector('.adm-avatar')
21+
expect(avatar).toBeVisible()
22+
const img = avatar?.querySelector('img')
23+
expect(img?.getAttribute('src')).toBeFalsy()
24+
expect(document.querySelectorAll('.adm-avatar-fallback')[0]).toBeVisible()
25+
})
26+
27+
test('whitespace src should show fallback', () => {
28+
render(<Avatar src={' '} />)
29+
expect(document.querySelectorAll('.adm-avatar-fallback')[0]).toBeVisible()
30+
})
1731
})

src/components/image-viewer/tests/image-viewer.test.tsx

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ describe('ImageViewer.Multi', () => {
176176
})
177177
const onIndexChange = jest.fn()
178178

179-
const { getByText } = render(
179+
render(
180180
<ImageViewer.Multi
181181
visible
182182
defaultIndex={3}
@@ -195,9 +195,18 @@ describe('ImageViewer.Multi', () => {
195195
// need to wait image render.
196196
await act(() => new Promise(resolve => setTimeout(resolve, 2500)))
197197

198+
// 等待动画完成并确保指示器已更新
199+
await act(async () => {
200+
await new Promise(resolve => setTimeout(resolve, 100))
201+
})
202+
198203
const slides = document.querySelectorAll(`.${classPrefix}-control`)[3]
199204

200-
expect(getByText('4 / 4')).toBeInTheDocument()
205+
// 使用查询所有包含数字和斜杠的元素来查找指示器
206+
const indicatorElements = screen.getAllByText(/\d+\s*\/\s*\d+/)
207+
expect(
208+
indicatorElements.some(el => el.textContent?.includes('4 / 4'))
209+
).toBe(true)
201210

202211
mockDrag(slides as HTMLElement, [
203212
{
@@ -210,7 +219,17 @@ describe('ImageViewer.Multi', () => {
210219
clientX: 300,
211220
},
212221
])
213-
expect(getByText('3 / 4')).toBeInTheDocument()
222+
223+
// 等待拖拽完成并确保指示器已更新
224+
await act(async () => {
225+
await new Promise(resolve => setTimeout(resolve, 100))
226+
})
227+
228+
const indicatorElementsAfterDrag = screen.getAllByText(/\d+\s*\/\s*\d+/)
229+
expect(
230+
indicatorElementsAfterDrag.some(el => el.textContent?.includes('3 / 4'))
231+
).toBe(true)
232+
214233
await waitFor(() => expect(onIndexChange).toBeCalledTimes(1))
215234
await waitFor(() => expect(onIndexChange).toBeCalledWith(2))
216235

@@ -226,7 +245,18 @@ describe('ImageViewer.Multi', () => {
226245
},
227246
])
228247

229-
expect(screen.getByText('4 / 4')).toBeInTheDocument()
248+
// 等待拖拽完成并确保指示器已更新
249+
await act(async () => {
250+
await new Promise(resolve => setTimeout(resolve, 100))
251+
})
252+
253+
const indicatorElementsAfterDragBack = screen.getAllByText(/\d+\s*\/\s*\d+/)
254+
expect(
255+
indicatorElementsAfterDragBack.some(el =>
256+
el.textContent?.includes('4 / 4')
257+
)
258+
).toBe(true)
259+
230260
await waitFor(() => expect(onIndexChange).toBeCalledTimes(2))
231261
await waitFor(() => expect(onIndexChange).toBeCalledWith(3))
232262
})

src/components/image/image.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { mergeProps } from '../../utils/with-default-props'
2-
import React, { useState, useRef, useEffect } from 'react'
31
import type { ReactNode } from 'react'
4-
import { NativeProps, withNativeProps } from '../../utils/native-props'
2+
import React, { useEffect, useRef, useState } from 'react'
53
import { staged } from 'staged-components'
4+
import { NativeProps, withNativeProps } from '../../utils/native-props'
65
import { toCSSLength } from '../../utils/to-css-length'
7-
import { LazyDetector } from './lazy-detector'
86
import { useIsomorphicUpdateLayoutEffect } from '../../utils/use-isomorphic-update-layout-effect'
9-
import { ImageIcon } from './image-icon'
7+
import { mergeProps } from '../../utils/with-default-props'
108
import { BrokenImageIcon } from './broken-image-icon'
9+
import { ImageIcon } from './image-icon'
10+
import { LazyDetector } from './lazy-detector'
1111

1212
const classPrefix = `adm-image`
1313

@@ -84,7 +84,7 @@ export const Image = staged<ImageProps>(p => {
8484
}, [])
8585

8686
function renderInner() {
87-
if (failed) {
87+
if (failed || (src === undefined && !srcSet)) {
8888
return <>{props.fallback}</>
8989
}
9090
const img = (

src/components/image/tests/image.test.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react'
2-
import { fireEvent, render, act } from 'testing'
2+
import { act, fireEvent, render } from 'testing'
33
import Image from '../index'
44

55
const classPrefix = `adm-image`
@@ -48,8 +48,9 @@ describe('Image', () => {
4848
window.IntersectionObserver = mockIntersectionObserver
4949

5050
render(<Image src={demoSrc} lazy />)
51-
const img = document.querySelectorAll(`.${classPrefix}-img`)[0]
52-
expect(img).not.toHaveAttribute('src')
51+
// 初始状态下,由于懒加载,img 元素不应该存在
52+
let img = document.querySelectorAll(`.${classPrefix}-img`)[0]
53+
expect(img).toBeUndefined()
5354

5455
const calls = mockIntersectionObserver.mock.calls
5556
const [onChange] = calls[calls.length - 1]
@@ -60,6 +61,7 @@ describe('Image', () => {
6061
},
6162
])
6263
})
64+
img = document.querySelectorAll(`.${classPrefix}-img`)[0]
6365
expect(img).toHaveAttribute('src', demoSrc)
6466
})
6567

0 commit comments

Comments
 (0)