Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions desktop/src/features/messages/ui/MessageReactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,10 @@ function ReactionPopoverContent({ reaction }: { reaction: TimelineReaction }) {
<div className="max-w-[14rem] text-balance text-sm font-semibold leading-snug text-popover-foreground">
{userText} <span className="text-muted-foreground">reacted with</span>
</div>
<div className="mt-0.5 text-sm font-semibold leading-snug text-muted-foreground">
<div
className="mt-0.5 break-all text-sm font-semibold leading-snug text-muted-foreground"
data-testid="reaction-popover-name"
>
{displayName}
</div>
</div>
Expand Down Expand Up @@ -504,7 +507,7 @@ function ReactionPill({
align="start"
side="top"
sideOffset={6}
className="w-auto min-w-56 max-w-72 rounded-xl p-3"
className="w-72 rounded-xl p-3"
onMouseEnter={handleMouseEnter}
onMouseLeave={scheduleClose}
onOpenAutoFocus={(e) => e.preventDefault()}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
156 changes: 150 additions & 6 deletions desktop/tests/e2e/reaction-names.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,41 @@
import { expect, test } from "@playwright/test";
import * as fs from "node:fs";
import * as path from "node:path";
import { fileURLToPath } from "node:url";

import { waitForAnimations } from "../helpers/animations";
import { installMockBridge } from "../helpers/bridge";

const REACTION_TARGET_CONTENT = "React to me with a custom emoji";
const REACTION_TARGET_EVENT_ID = "d".repeat(64);
const BOB_PUBKEY =
"bb22a5299220cad76ffd46190ccbeede8ab5dc260faa28b6e5a2cb31b9aff260";
const MAX_REACTION_NAME =
"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl";
const MAX_REACTION_AVATAR_URL =
"https://cdn.example.test/avatars/teammate-alice.jpg";
const SHORT_REACTION_AVATAR_URL =
"https://cdn.example.test/avatars/bob-avatar.jpg";
const AVATAR_FIXTURES = new Map([
[
MAX_REACTION_AVATAR_URL,
fileURLToPath(
new URL(
"./fixtures/reaction-popover/teammate-alice.jpg",
import.meta.url,
),
),
],
[
SHORT_REACTION_AVATAR_URL,
fileURLToPath(
new URL("./fixtures/reaction-popover/bob-avatar.jpg", import.meta.url),
),
],
]);
const SCREENSHOT_DIR =
process.env.REACTION_POPOVER_SCREENSHOT_DIR ??
"test-results/reaction-popover-screenshots";

function reactionTargetRow(page: import("@playwright/test").Page) {
return page
Expand All @@ -14,8 +44,56 @@ function reactionTargetRow(page: import("@playwright/test").Page) {
.last();
}

async function waitForImage(
image: import("@playwright/test").Locator,
): Promise<void> {
await expect(image).toBeVisible();
await expect
.poll(() =>
image.evaluate(
(element) =>
element instanceof HTMLImageElement &&
element.complete &&
element.naturalWidth > 0,
),
)
.toBe(true);
}

async function capturePopover(
page: import("@playwright/test").Page,
popover: import("@playwright/test").Locator,
filename: string,
): Promise<void> {
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
await waitForAnimations(page);
await popover.screenshot({
animations: "disabled",
path: path.join(SCREENSHOT_DIR, filename),
});
}

test.beforeEach(async ({ page }) => {
await installMockBridge(page);
await page.route("https://cdn.example.test/avatars/**", async (route) => {
const fixturePath = AVATAR_FIXTURES.get(route.request().url());
if (!fixturePath) {
await route.abort("blockedbyclient");
return;
}
await route.fulfill({
body: fs.readFileSync(fixturePath),
contentType: "image/jpeg",
});
});
await installMockBridge(page, {
searchProfiles: [
{
pubkey: BOB_PUBKEY,
displayName: "bob",
avatarUrl: SHORT_REACTION_AVATAR_URL,
},
],
});
});

test("reaction popover resolves a reactor with no authored message in the window", async ({
Expand All @@ -33,21 +111,87 @@ test("reaction popover resolves a reactor with no authored message in the window
);

await page.evaluate(
({ pubkey, targetId }) => {
({ pubkey, targetId, avatarUrl }) => {
window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
channelName: "general",
content: "🎉",
extraTags: [["e", targetId]],
content: ":react:",
extraTags: [
["e", targetId],
["emoji", "react", avatarUrl],
],
kind: 7,
pubkey,
});
},
{ pubkey: BOB_PUBKEY, targetId: REACTION_TARGET_EVENT_ID },
{
avatarUrl: SHORT_REACTION_AVATAR_URL,
pubkey: BOB_PUBKEY,
targetId: REACTION_TARGET_EVENT_ID,
},
);

const row = reactionTargetRow(page);
const pill = row.getByRole("button", { name: "Toggle 🎉 reaction" });
const pill = row.getByRole("button", { name: "Toggle :react: reaction" });
await expect(pill).toBeVisible();
await pill.hover();
await expect(page.getByText("bob reacted with")).toBeVisible();
const popover = page
.locator("[data-radix-popper-content-wrapper]")
.filter({ hasText: "bob reacted with" });
const avatar = popover.locator(`img[src="${SHORT_REACTION_AVATAR_URL}"]`);
await waitForImage(avatar);
await capturePopover(page, popover, "short-name-after.png");
});

test("maximum-length reaction name wraps inside a fixed-width popover", async ({
page,
}) => {
await page.goto("/");
await page.getByTestId("channel-general").click();
await expect(page.getByTestId("chat-title")).toHaveText("general");
await page.waitForFunction(
() =>
window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({
channelName: "general",
kind: 7,
}) === true,
);

const reaction = `:${MAX_REACTION_NAME}:`;
await page.evaluate(
({ content, targetId, avatarUrl }) => {
window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
channelName: "general",
content,
extraTags: [
["e", targetId],
["emoji", content.slice(1, -1), avatarUrl],
],
kind: 7,
});
},
{
avatarUrl: MAX_REACTION_AVATAR_URL,
content: reaction,
targetId: REACTION_TARGET_EVENT_ID,
},
);

const pill = reactionTargetRow(page).getByRole("button", {
name: `Toggle ${reaction} reaction`,
});
await expect(pill).toBeVisible();
await pill.focus();

const popover = page
.locator("[data-radix-popper-content-wrapper]")
.filter({ hasText: reaction });
await expect(popover).toBeVisible();
await expect(popover).toHaveCSS("width", "288px");
const reactionName = popover.getByTestId("reaction-popover-name");
await expect(reactionName).toHaveCSS("word-break", "break-all");
await expect(reactionName).toHaveText(reaction);
const avatar = popover.locator(`img[src="${MAX_REACTION_AVATAR_URL}"]`);
await waitForImage(avatar);
await capturePopover(page, popover, "max-length-after.png");
});
Loading