diff --git a/app/features/user-card/components/UserCard.tsx b/app/features/user-card/components/UserCard.tsx index 4bd3b6341..648d121aa 100644 --- a/app/features/user-card/components/UserCard.tsx +++ b/app/features/user-card/components/UserCard.tsx @@ -95,15 +95,39 @@ export function UserCard({ withMutualFriends?: boolean; children: React.ReactNode; }) { - const { t } = useTranslation(["common", "q"]); const lookedUpData = useUserCardData(userId); const data = dataProp ?? lookedUpData; + if (!data) return <>{children}; + + // keyed so friendship state doesn't carry over to another user e.g. when navigating between user pages + return ( + + {children} + + ); +} + +function UserCardPopover({ + data, + withMutualFriends, + children, +}: { + data: UserCardData; + withMutualFriends: boolean; + children: React.ReactNode; +}) { + const { t } = useTranslation(["common", "q"]); + // on narrow viewports the card is placed vertically so React Aria can shift it to stay on-screen const placement = useLayoutSize() === "mobile" ? "bottom" : "right"; const user = useUser(); - const isOwnCard = user?.id === data?.id; + const isOwnCard = user?.id === data.id; const [isOpen, setIsOpen] = React.useState(false); // outside the popover so the modals survive it closing when they take focus @@ -120,7 +144,6 @@ export function UserCard({ if (!nextIsOpen) return; if (friendshipLoadedRef.current) return; if (isOwnCard) return; - if (typeof data?.id !== "number") return; friendshipLoadedRef.current = true; fetcher.load( @@ -148,8 +171,6 @@ export function UserCard({ setIsReportDialogOpen(true); }; - if (!data) return <>{children}; - return ( <> + this.locators.sendFriendRequestButton.click(), + ); + } + + async close() { + await this.page.keyboard.press("Escape"); + await expect(this.locators.banner).not.toBeVisible(); + } + async openReportDialog() { await this.locators.reportUserButton.click(); return new ReportUserDialog(this.page); diff --git a/e2e/pages/user/user-page.ts b/e2e/pages/user/user-page.ts index 2454441b8..736d13ebc 100644 --- a/e2e/pages/user/user-page.ts +++ b/e2e/pages/user/user-page.ts @@ -3,6 +3,7 @@ import { userPage } from "~/utils/urls"; import { navigate } from "../../helpers/playwright"; import { TeamPage } from "../team/team-page"; import { TopSearchPlayerPage } from "../top-search/top-search-player-page"; +import { UserCard } from "./user-card"; import { UserEditProfilePage } from "./user-edit-profile-page"; import { UserEditWidgetsPage } from "./user-edit-widgets-page"; import { UserResultsPage } from "./user-results-page"; @@ -76,6 +77,14 @@ export class UserPage { return this.page.getByRole("heading", { name: username }); } + /** Opens the user card from the username heading. */ + openUserCard(username: string) { + return UserCard.open( + this.page, + this.usernameHeading(username).getByRole("button"), + ); + } + async openEditProfile() { await this.locators.editProfileButton.click(); return new UserEditProfilePage(this.page); diff --git a/e2e/user-card.spec.ts b/e2e/user-card.spec.ts index a0cc0a922..c343a0b0a 100644 --- a/e2e/user-card.spec.ts +++ b/e2e/user-card.spec.ts @@ -1,10 +1,12 @@ -import { NZAP_TEST_ID } from "~/db/seed/constants"; +import { NZAP_TEST_DISCORD_ID, NZAP_TEST_ID } from "~/db/seed/constants"; import { ADMIN_ID } from "~/features/admin/admin-constants"; import { SENDOUQ_LOOKING_PAGE } from "~/utils/urls"; import { expect, impersonate, test } from "./helpers/playwright"; import { FriendsPage } from "./pages/friends/friends-page"; import { LFGPage } from "./pages/lfg/lfg-page"; +import { GlobalSearchDialog } from "./pages/search/global-search-dialog"; import { SendouQLookingPage } from "./pages/sendouq/sendouq-looking-page"; +import { UserPage } from "./pages/user/user-page"; test.describe("User card", () => { test("edits banner and bio from the looking page", async ({ @@ -126,4 +128,35 @@ test.describe("User card friend request", () => { await expect(card.locators.pendingFriendRequestButton).toBeDisabled(); await expect(card.locators.acceptFriendRequestButton).not.toBeVisible(); }); + + test("sending a request does not mark another user's card pending after navigating between user pages", async ({ + page, + factories, + }) => { + const otherUsername = "FriendTarget"; + await factories.UserFactory.create({ discordName: otherUsername }); + + await impersonate(page); + + const userPage = new UserPage(page); + await userPage.goto(NZAP_TEST_DISCORD_ID); + + const nzapCard = await userPage.openUserCard("N-ZAP"); + await nzapCard.sendFriendRequest(); + await expect(nzapCard.locators.pendingFriendRequestButton).toBeVisible(); + await nzapCard.close(); + + const search = new GlobalSearchDialog(page); + await search.open(); + await search.selectType("users"); + await search.search(otherUsername); + await search.selectOption(otherUsername); + await expect(userPage.usernameHeading(otherUsername)).toBeVisible(); + + const otherCard = await userPage.openUserCard(otherUsername); + await expect(otherCard.locators.sendFriendRequestButton).toBeVisible(); + await expect( + otherCard.locators.pendingFriendRequestButton, + ).not.toBeVisible(); + }); });