| author | |
| committer | |
| log | df466c79810046d20c0f3ccc4e51709c5703814e |
| tree | 22a737910002d2864f390e28b0a86a7950226cea |
| parent | 573a8f2f30d59c528997daf1e02bdb58e0d9dc54 |
| signature | Signed by PGP key B5690EEEBB952194 |
2 files changed, 33 insertions(+), 3 deletions(-)
apps/web/src/components/pullRequest/pullRequestPresentation.test.tsx created+28| ... | ... | @@ -0,0 +1,28 @@ |
| 1 | import { act } from "react"; | |
| 2 | import { create, type ReactTestRenderer } from "react-test-renderer"; | |
| 3 | import { afterEach, expect, it, vi } from "vite-plus/test"; | |
| 4 | ||
| 5 | import { PullRequestActorAvatar } from "./pullRequestPresentation"; | |
| 6 | ||
| 7 | let renderer: ReactTestRenderer | undefined; | |
| 8 | ||
| 9 | afterEach(async () => { | |
| 10 | await act(async () => renderer?.unmount()); | |
| 11 | vi.unstubAllGlobals(); | |
| 12 | }); | |
| 13 | ||
| 14 | it("falls back to the actor initial when a remote avatar fails", async () => { | |
| 15 | vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); | |
| 16 | await act(async () => { | |
| 17 | renderer = create( | |
| 18 | <PullRequestActorAvatar | |
| 19 | actor={{ login: "octocat", name: null, avatarUrl: "https://ghe.example/octocat.png" }} | |
| 20 | />, | |
| 21 | ); | |
| 22 | }); | |
| 23 | ||
| 24 | await act(async () => renderer!.root.findByType("img").props.onError()); | |
| 25 | ||
| 26 | expect(renderer!.root.findAllByType("img")).toHaveLength(0); | |
| 27 | expect(renderer!.root.findByType("span").children).toEqual(["O"]); | |
| 28 | }); |
apps/web/src/components/pullRequest/pullRequestPresentation.tsx+5-3| ... | ... | @@ -14,7 +14,7 @@ import { |
| 14 | 14 | CircleXIcon, |
| 15 | 15 | UserCheckIcon, |
| 16 | 16 | } from "lucide-react"; |
| 17 | import { Children, isValidElement, type ReactNode } from "react"; | |
| 17 | import { Children, isValidElement, type ReactNode, useState } from "react"; | |
| 18 | 18 | |
| 19 | 19 | import { cn } from "~/lib/utils"; |
| 20 | 20 | |
| ... | ... | @@ -352,8 +352,9 @@ export function PullRequestActorAvatar({ |
| 352 | 352 | }) { |
| 353 | 353 | const login = actor?.login ?? "ghost"; |
| 354 | 354 | const avatarUrl = actor?.avatarUrl ?? null; |
| 355 | return avatarUrl === null ? ( | |
| 356 | // Not every host reports an avatar, so the initial stands in where none arrives. | |
| 355 | const [failedAvatarUrl, setFailedAvatarUrl] = useState<string | null>(null); | |
| 356 | return avatarUrl === null || failedAvatarUrl === avatarUrl ? ( | |
| 357 | // Not every host reports an avatar, and a private host may refuse the browser's request. | |
| 357 | 358 | <span |
| 358 | 359 | aria-hidden |
| 359 | 360 | className={cn( |
| ... | ... | @@ -370,6 +371,7 @@ export function PullRequestActorAvatar({ |
| 370 | 371 | src={avatarUrl} |
| 371 | 372 | loading="lazy" |
| 372 | 373 | className={cn("size-4 shrink-0 rounded-full bg-muted object-cover", className)} |
| 374 | onError={() => setFailedAvatarUrl(avatarUrl)} | |
| 373 | 375 | /> |
| 374 | 376 | ); |
| 375 | 377 | } |