Skip to content

Commit a771e44

Browse files
authored
fix(react-avatar): prevent spreading of custom props on html element (microsoft#36017)
1 parent 4adb3ad commit a771e44

3 files changed

Lines changed: 74 additions & 5 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "fix: prevent spreading of custom props on html element",
4+
"packageName": "@fluentui/react-avatar",
5+
"email": "dmytrokirpa@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import * as React from 'react';
2+
import { renderHook } from '@testing-library/react-hooks';
3+
4+
import { useAvatar_unstable } from './useAvatar';
5+
6+
describe('useAvatar', () => {
7+
let ref: React.RefObject<HTMLElement | null>;
8+
9+
beforeEach(() => {
10+
ref = React.createRef<HTMLElement>();
11+
});
12+
13+
it('should return state with default props', () => {
14+
const { result } = renderHook(() => useAvatar_unstable({ name: 'John Doe' }, ref));
15+
16+
expect(result.current).toMatchObject({
17+
image: undefined,
18+
initials: expect.objectContaining({
19+
children: 'JD',
20+
}),
21+
});
22+
});
23+
24+
it('should return state with custom props', () => {
25+
const props = {
26+
name: 'John Doe',
27+
image: { src: '/avatar.png' },
28+
};
29+
30+
const { result } = renderHook(() => useAvatar_unstable(props, ref));
31+
32+
expect(result.current).toMatchObject({
33+
image: expect.objectContaining({
34+
alt: '',
35+
src: props.image.src,
36+
}),
37+
initials: expect.objectContaining({
38+
children: 'JD',
39+
}),
40+
});
41+
42+
// Custom props should not be spread on the root element
43+
expect(result.current.root).not.toHaveProperty('image');
44+
expect(result.current.root).not.toHaveProperty('initials');
45+
});
46+
47+
it('should be possible to remove default icon', () => {
48+
const props = {
49+
name: '',
50+
icon: { children: null },
51+
};
52+
53+
const { result } = renderHook(() => useAvatar_unstable(props, ref));
54+
55+
expect(result.current).toMatchObject({
56+
image: undefined,
57+
icon: {
58+
children: null,
59+
},
60+
});
61+
});
62+
});

packages/react-components/react-avatar/library/src/components/Avatar/useAvatar.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ export const useAvatar_unstable = (props: AvatarProps, ref: React.Ref<HTMLElemen
4646
});
4747
}
4848

49-
if (state.icon) {
50-
state.icon.children ??= <PersonRegular />;
49+
if (state.icon && !state.icon.hasOwnProperty('children')) {
50+
state.icon.children = <PersonRegular />;
5151
}
5252

5353
const badge: AvatarState['badge'] = slot.optional(props.badge, {
@@ -110,7 +110,7 @@ export const useAvatar_unstable = (props: AvatarProps, ref: React.Ref<HTMLElemen
110110
*/
111111
export const useAvatarBase_unstable = (props: AvatarBaseProps, ref?: React.Ref<HTMLElement>): AvatarBaseState => {
112112
const { dir } = useFluent();
113-
const { name, ...rest } = props;
113+
const { name, image: imageProp, initials: initialsProp, ...rest } = props;
114114

115115
const baseId = useId('avatar-');
116116

@@ -126,7 +126,7 @@ export const useAvatarBase_unstable = (props: AvatarBaseProps, ref?: React.Ref<H
126126

127127
const [imageHidden, setImageHidden] = React.useState<true | undefined>(undefined);
128128

129-
let image: AvatarBaseState['image'] = slot.optional(props.image, {
129+
let image: AvatarBaseState['image'] = slot.optional(imageProp, {
130130
defaultProps: { alt: '', role: 'presentation', 'aria-hidden': true, hidden: imageHidden },
131131
elementType: 'img',
132132
});
@@ -143,7 +143,7 @@ export const useAvatarBase_unstable = (props: AvatarBaseProps, ref?: React.Ref<H
143143
}
144144

145145
// Resolve the initials slot, defaulted to getInitials
146-
let initials: AvatarBaseState['initials'] = slot.optional(props.initials, {
146+
let initials: AvatarBaseState['initials'] = slot.optional(initialsProp, {
147147
renderByDefault: true,
148148
defaultProps: {
149149
children: getInitials(name, dir === 'rtl'),

0 commit comments

Comments
 (0)