Skip to content
35 changes: 35 additions & 0 deletions components/Avatar/Avatar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Avatar from './Avatar';
import { render, screen } from '@testing-library/react';

jest.mock('@radix-ui/react-avatar', () => {
Copy link
Member

Choose a reason for hiding this comment

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

Just FYI for anyone coming across this. My first instinct was to ask why are we mocking this as you always want to mock as little as possible or not at all so we are testing the actual implementation.

In this case, we mock Radix’s Avatar.Image because in JSDOM, what we use in Jest tests, image load events don’t fire as it's not a real browser environment, so only the fallback is ever shown in tests if we don't mock.

const actual = jest.requireActual('@radix-ui/react-avatar');
return ({
...actual,
Image: ({src, alt, ...props}: {src?: string, alt?: string}) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove ...props from the Image mock. When testing, you should only mock the necessary behavior. For the Avatar component, you're currently only testing for src and alt. The other properties of Image aren't needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Remove the optional ? on the alt?. This property will always be included in both the AvatarImage and AvatarFallback.

return <img data-testid="avatar-image" src={src} alt={alt} {...props}/>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't add data-testids inside components used in testing. While you fetched the element by role, which is good, the id still isn't necessary for the mock.

If anything, this id should have been added to the AvatarImage component.

}
})
});

describe('Avatar Component', () => {

it('Renders the component with user image using valid src attribute', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should the fallback component show here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not for this scenario. I wanted to test that the user provided image is correctly added to the document.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Add an exception to check that fallback doesn't show in this default scenario.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interestingly the fallback image is still showing in the document for this scenario. I tried using .not.toBeVisible() method as well as .not.toBeInTheDocument() but both tests fail:

1st attempt:

+ const avatarElementFallbackImage = screen.getByTestId('avatar-fallback')
+ expect(avatarElementFallbackImage).not.toBeVisible();

2nd attempt:

+ const avatarElementFallbackImage = screen.getByTestId('avatar-fallback')
+ expect(avatarElementFallbackImage).not.toBeInTheDocument();

I tried looking at screen.debug() and it's showing both elements in the JSDOM. Are both of these supposed to show up like this?

Screen-debug-avatar-component

render(<Avatar userAvatar='https://mdbcdn.b-cdn.net/img/new/avatars/2.webp' />);

const avatarElementWithImage = screen.getByRole('img', { name: 'user avatar'});
Copy link
Collaborator

Choose a reason for hiding this comment

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

Follow how we're doing the other components by adding a data-testid attribute.

expect(avatarElementWithImage).toBeInTheDocument();
expect(avatarElementWithImage).toHaveAttribute(
'src',
'https://mdbcdn.b-cdn.net/img/new/avatars/2.webp',
);

});

it('Renders the fallback image when src is undefined', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

What about when there's a string given bug the image is not found?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That makes sense, I'll add this test scenario.

render(<Avatar userAvatar={undefined} />);

const avatarElementFallbackImage = screen.getByRole('img', { name: 'default avatar'});
expect(avatarElementFallbackImage).toBeInTheDocument();
});

});
2 changes: 1 addition & 1 deletion components/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const Avatar = ({
}): JSX.Element => {
return (
<AvatarBody>
<AvatarImage src={userAvatar} alt="" />
<AvatarImage src={userAvatar} alt="user avatar" />
Copy link
Member

Choose a reason for hiding this comment

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

refactor (a11y): "user avatar" is too generic. If the avatar conveys meaning, add more context, like the username, otherwise leave the alt test to "".

Suggested change
<AvatarImage src={userAvatar} alt="user avatar" />
<AvatarImage src={userAvatar} alt=`profile image for user ${username}` />

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think adding more context with the username makes sense. After adding username as a prop:

const Avatar = ({ userAvatar, username }: { userAvatar: string | undefined, username: string })

I believe I would need to update each instance where the Avatar component is used:

<Avatar userAvatar={avatar} />

<Avatar data-testid="avatar" userAvatar={profile?.avatar} />

<Avatar userAvatar={member.member.avatar} />

I want to make sure this correct and okay to do inside the same PR as I'm starting to change multiple files. For the UserDropdownMenu component it gets more complicated to pass a username as well.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, this is only a unit test task. This should be removed into an A11Y ticket. I want to keep things simple for your first test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I'll just leave the AvatarImage alt text as it was prior to my edits.

<AvatarFallback>
<img
src="https://static.vecteezy.com/system/resources/previews/024/183/525/non_2x/avatar-of-a-man-portrait-of-a-young-guy-illustration-of-male-character-in-modern-color-style-vector.jpg"
Expand Down
Loading