Fix custom emoji status in profile popover (#874)

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
Wes
2026-06-05 09:26:13 -07:00
committed by GitHub
co-authored by Pinky
parent b295f51c90
commit 5bdac0566f
4 changed files with 142 additions and 6 deletions
+1
View File
@@ -25,6 +25,7 @@ export default defineConfig({
"**/channel-browser.spec.ts",
"**/messaging.spec.ts",
"**/custom-emoji.spec.ts",
"**/profile-custom-emoji-status.spec.ts",
"**/custom-emoji-screenshots.spec.ts",
"**/channel-mute-screenshots.spec.ts",
"**/channel-star-screenshots.spec.ts",
@@ -6,6 +6,7 @@ import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar";
import { PresenceDot } from "@/features/presence/ui/PresenceBadge";
import { getPresenceLabel } from "@/features/presence/lib/presence";
import { SetStatusDialog } from "@/features/user-status/ui/SetStatusDialog";
import { StatusEmoji } from "@/features/user-status/ui/StatusEmoji";
import type { PresenceStatus } from "@/shared/api/types";
import { isMacPlatform } from "@/shared/lib/platform";
@@ -179,7 +180,10 @@ export function ProfilePopover({
{hasUserStatus ? (
<span className="flex min-w-0 flex-1 items-center gap-1 truncate text-popover-foreground">
{userStatusEmoji ? (
<span className="shrink-0">{userStatusEmoji}</span>
<StatusEmoji
className="h-3.5 w-3.5 shrink-0"
value={userStatusEmoji}
/>
) : null}
<span className="truncate">{userStatusText}</span>
</span>
+90 -5
View File
@@ -11,6 +11,7 @@ import {
import {
KIND_STREAM_MESSAGE_EDIT,
KIND_SYSTEM_MESSAGE,
KIND_USER_STATUS,
} from "@/shared/constants/kinds";
import type {
RawAcpProviderCatalogEntry,
@@ -393,6 +394,13 @@ type MockSubscription = {
kinds: number[] | null;
};
type MockFilter = {
"#d"?: string[];
"#h"?: string[];
authors?: string[];
kinds?: number[];
};
type MockSocket = {
handler: WsHandler;
subscriptions: Map<string, MockSubscription>;
@@ -1230,6 +1238,7 @@ const mockChannels: MockChannel[] = [
];
const mockMessages = new Map<string, RelayEvent[]>();
const mockUserStatuses: RelayEvent[] = [];
let mockRelayMembers: RawRelayMember[] = [];
const mockSockets = new Map<number, MockSocket>();
let mockWebsocketSendMutexWedged = false;
@@ -1923,6 +1932,17 @@ function emitMockLiveEvent(channelId: string, event: RelayEvent) {
}
}
function emitMockGlobalEvent(event: RelayEvent) {
for (const socket of mockSockets.values()) {
for (const [subId, subscription] of socket.subscriptions) {
if (subscription.kinds && !subscription.kinds.includes(event.kind)) {
continue;
}
sendWsText(socket.handler, ["EVENT", subId, event]);
}
}
}
function hasMockLiveSubscription(channelId: string, kind?: number) {
for (const socket of mockSockets.values()) {
for (const subscription of socket.subscriptions.values()) {
@@ -1954,6 +1974,46 @@ function recordMockMessage(channelId: string, event: RelayEvent) {
touchMockChannel(channel);
}
function resetMockUserStatuses() {
mockUserStatuses.length = 0;
}
function recordMockUserStatus(event: RelayEvent) {
const dTag = event.tags.find((tag) => tag[0] === "d")?.[1];
if (dTag) {
const index = mockUserStatuses.findIndex(
(stored) =>
stored.pubkey.toLowerCase() === event.pubkey.toLowerCase() &&
stored.tags.some((tag) => tag[0] === "d" && tag[1] === dTag),
);
if (index >= 0) {
mockUserStatuses.splice(index, 1);
}
}
mockUserStatuses.push(event);
}
function filterMockUserStatuses(filter: MockFilter) {
const authors = filter.authors?.map((author) => author.toLowerCase());
const dTags = filter["#d"];
return mockUserStatuses
.filter((event) => {
if (authors && !authors.includes(event.pubkey.toLowerCase())) {
return false;
}
if (
dTags &&
!event.tags.some((tag) => tag[0] === "d" && dTags.includes(tag[1]))
) {
return false;
}
return true;
})
.sort((a, b) => b.created_at - a.created_at);
}
function emitMockChannelMessage(
channelId: string,
content: string,
@@ -5022,11 +5082,7 @@ function sendToMockSocket(args: {
return;
}
const filter = rest[1] as {
"#h"?: string[];
kinds?: number[];
authors?: string[];
};
const filter = rest[1] as MockFilter;
if (filter.kinds?.includes(13534)) {
sendWsText(socket.handler, [
"EVENT",
@@ -5052,6 +5108,14 @@ function sendToMockSocket(args: {
return;
}
if (filter.kinds?.includes(KIND_USER_STATUS)) {
for (const statusEvent of filterMockUserStatuses(filter)) {
sendWsText(socket.handler, ["EVENT", subId, statusEvent]);
}
sendWsText(socket.handler, ["EOSE", subId]);
return;
}
const channelId = filter["#h"]?.[0];
if (!channelId) {
sendWsText(socket.handler, ["EOSE", subId]);
@@ -5109,6 +5173,26 @@ function sendToMockSocket(args: {
return;
}
if (event.kind === KIND_USER_STATUS) {
const hasGeneralDTag = event.tags.some(
(tag) => tag[0] === "d" && tag[1] === "general",
);
if (!hasGeneralDTag) {
sendWsText(socket.handler, [
"OK",
event.id,
false,
"invalid: user status missing d tag.",
]);
return;
}
recordMockUserStatus(event);
emitMockGlobalEvent(event);
sendWsText(socket.handler, ["OK", event.id, true, ""]);
return;
}
const channelId = getChannelIdFromTags(event.tags);
if (!channelId) {
sendWsText(socket.handler, [
@@ -5152,6 +5236,7 @@ export function maybeInstallE2eTauriMocks() {
resetMockTeams();
resetMockWorkflows();
resetMockMesh();
resetMockUserStatuses();
mockWebsocketSendMutexWedged = false;
mockWindows("main");
window.__SPROUT_E2E_COMMANDS__ = [];
@@ -0,0 +1,46 @@
import { expect, test } from "@playwright/test";
import { installMockBridge } from "../helpers/bridge";
const SHORTCODE = "sprout";
const STATUS_TEXT = "testing custom status";
async function openProfilePopover(page: import("@playwright/test").Page) {
await page.getByTestId("open-settings").click();
await expect(page.getByTestId("profile-popover")).toBeVisible();
}
test.beforeEach(async ({ page }) => {
await installMockBridge(page);
const PNG = Buffer.from(
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGUlEQVR4nGMwuBPxnxLMMGrAqAGjBgwXAwBwOGMf1PPhVwAAAABJRU5ErkJggg==",
"base64",
);
await page.route("https://example.com/e2e/**", (route) =>
route.fulfill({ contentType: "image/png", body: PNG }),
);
});
test("profile popover renders a custom emoji status as an image", async ({
page,
}) => {
await page.goto("/");
await openProfilePopover(page);
await page.getByTestId("profile-popover-set-status").click();
await expect(page.getByTestId("set-status-dialog")).toBeVisible();
await page.getByLabel("Choose status emoji").click();
const picker = page.locator("em-emoji-picker");
await picker.locator("input[type='search']").fill(SHORTCODE);
await picker.locator(`button[title='${SHORTCODE}']`).first().click();
await page.getByTestId("set-status-input").fill(STATUS_TEXT);
await page.getByTestId("set-status-save").click();
await openProfilePopover(page);
const statusButton = page.getByTestId("profile-popover-set-status");
await expect(statusButton).toContainText(STATUS_TEXT);
await expect(statusButton.locator(`img[alt=":${SHORTCODE}:"]`)).toBeVisible();
await expect(statusButton).not.toContainText(`:${SHORTCODE}:`);
});