diff --git a/.yarn/versions/19bdd400.yml b/.yarn/versions/19bdd400.yml new file mode 100644 index 0000000000..50d060d514 --- /dev/null +++ b/.yarn/versions/19bdd400.yml @@ -0,0 +1,5 @@ +releases: + "@radix-ui/react-avatar": patch + +declined: + - primitives diff --git a/packages/react/avatar/src/Avatar.test.tsx b/packages/react/avatar/src/Avatar.test.tsx index e1705c6f36..4e71f84ed3 100644 --- a/packages/react/avatar/src/Avatar.test.tsx +++ b/packages/react/avatar/src/Avatar.test.tsx @@ -1,6 +1,6 @@ import { axe } from 'jest-axe'; import type { RenderResult } from '@testing-library/react'; -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import * as Avatar from '@radix-ui/react-avatar'; const ROOT_TEST_ID = 'avatar-root'; @@ -100,3 +100,90 @@ describe('given an Avatar with fallback and delayed render', () => { expect(fallback).toBeInTheDocument(); }); }); + +describe('given an Avatar with an image that only works when referrerPolicy=no-referrer', () => { + let rendered: RenderResult; + const orignalGlobalImage = window.Image; + + beforeAll(() => { + (window.Image as any) = class MockImage { + onload: () => void = () => {}; + onerror: () => void = () => {}; + src: string = ''; + referrerPolicy: string | undefined; + constructor() { + setTimeout(() => { + if (this.referrerPolicy === 'no-referrer') { + this.onload(); + } else { + this.onerror(); + } + }, DELAY); + return this; + } + }; + }); + + afterAll(() => { + window.Image = orignalGlobalImage; + }); + + describe('referrerPolicy=no-referrer', () => { + beforeEach(() => { + rendered = render( + + {FALLBACK_TEXT} + + + ); + }); + + it('should render the fallback initially', () => { + const fallback = rendered.queryByText(FALLBACK_TEXT); + expect(fallback).toBeInTheDocument(); + }); + + it('should not render the image initially', () => { + const image = rendered.queryByRole('img'); + expect(image).not.toBeInTheDocument(); + }); + + it('should render the image after it has loaded', async () => { + const image = await rendered.findByRole('img'); + expect(image).toBeInTheDocument(); + }); + + it('should have alt text on the image', async () => { + const image = await rendered.findByAltText(IMAGE_ALT_TEXT); + expect(image).toBeInTheDocument(); + }); + }); + + describe('referrerPolicy=origin', () => { + beforeEach(() => { + rendered = render( + + {FALLBACK_TEXT} + + + ); + }); + + it('should render the fallback initially', () => { + const fallback = rendered.queryByText(FALLBACK_TEXT); + expect(fallback).toBeInTheDocument(); + }); + + it('should never render the image', async () => { + try { + await waitFor(() => rendered.getByRole('img'), { + timeout: DELAY + 100, + }); + } catch (error) { + expect(error).toBeInstanceOf(Error); + expect((error as Error).name).toBe('TestingLibraryElementError'); + expect((error as Error).message).toContain('Unable to find role="img"'); + } + }); + }); +}); diff --git a/packages/react/avatar/src/Avatar.tsx b/packages/react/avatar/src/Avatar.tsx index 3749fc218a..53841b7f0a 100644 --- a/packages/react/avatar/src/Avatar.tsx +++ b/packages/react/avatar/src/Avatar.tsx @@ -62,7 +62,7 @@ const AvatarImage = React.forwardRef( (props: ScopedProps, forwardedRef) => { const { __scopeAvatar, src, onLoadingStatusChange = () => {}, ...imageProps } = props; const context = useAvatarContext(IMAGE_NAME, __scopeAvatar); - const imageLoadingStatus = useImageLoadingStatus(src); + const imageLoadingStatus = useImageLoadingStatus(src, imageProps.referrerPolicy); const handleLoadingStatusChange = useCallbackRef((status: ImageLoadingStatus) => { onLoadingStatusChange(status); context.onImageLoadingStatusChange(status); @@ -116,7 +116,7 @@ AvatarFallback.displayName = FALLBACK_NAME; /* -----------------------------------------------------------------------------------------------*/ -function useImageLoadingStatus(src?: string) { +function useImageLoadingStatus(src?: string, referrerPolicy?: React.HTMLAttributeReferrerPolicy) { const [loadingStatus, setLoadingStatus] = React.useState('idle'); useLayoutEffect(() => { @@ -137,11 +137,14 @@ function useImageLoadingStatus(src?: string) { image.onload = updateStatus('loaded'); image.onerror = updateStatus('error'); image.src = src; + if (referrerPolicy) { + image.referrerPolicy = referrerPolicy; + } return () => { isMounted = false; }; - }, [src]); + }, [src, referrerPolicy]); return loadingStatus; }