mirror of
https://github.com/block/buzz.git
synced 2026-10-31 06:21:34 +00:00
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:
@@ -25,6 +25,7 @@ export default defineConfig({
|
|||||||
"**/channel-browser.spec.ts",
|
"**/channel-browser.spec.ts",
|
||||||
"**/messaging.spec.ts",
|
"**/messaging.spec.ts",
|
||||||
"**/custom-emoji.spec.ts",
|
"**/custom-emoji.spec.ts",
|
||||||
|
"**/profile-custom-emoji-status.spec.ts",
|
||||||
"**/custom-emoji-screenshots.spec.ts",
|
"**/custom-emoji-screenshots.spec.ts",
|
||||||
"**/channel-mute-screenshots.spec.ts",
|
"**/channel-mute-screenshots.spec.ts",
|
||||||
"**/channel-star-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 { PresenceDot } from "@/features/presence/ui/PresenceBadge";
|
||||||
import { getPresenceLabel } from "@/features/presence/lib/presence";
|
import { getPresenceLabel } from "@/features/presence/lib/presence";
|
||||||
import { SetStatusDialog } from "@/features/user-status/ui/SetStatusDialog";
|
import { SetStatusDialog } from "@/features/user-status/ui/SetStatusDialog";
|
||||||
|
import { StatusEmoji } from "@/features/user-status/ui/StatusEmoji";
|
||||||
import type { PresenceStatus } from "@/shared/api/types";
|
import type { PresenceStatus } from "@/shared/api/types";
|
||||||
import { isMacPlatform } from "@/shared/lib/platform";
|
import { isMacPlatform } from "@/shared/lib/platform";
|
||||||
|
|
||||||
@@ -179,7 +180,10 @@ export function ProfilePopover({
|
|||||||
{hasUserStatus ? (
|
{hasUserStatus ? (
|
||||||
<span className="flex min-w-0 flex-1 items-center gap-1 truncate text-popover-foreground">
|
<span className="flex min-w-0 flex-1 items-center gap-1 truncate text-popover-foreground">
|
||||||
{userStatusEmoji ? (
|
{userStatusEmoji ? (
|
||||||
<span className="shrink-0">{userStatusEmoji}</span>
|
<StatusEmoji
|
||||||
|
className="h-3.5 w-3.5 shrink-0"
|
||||||
|
value={userStatusEmoji}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<span className="truncate">{userStatusText}</span>
|
<span className="truncate">{userStatusText}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
KIND_STREAM_MESSAGE_EDIT,
|
KIND_STREAM_MESSAGE_EDIT,
|
||||||
KIND_SYSTEM_MESSAGE,
|
KIND_SYSTEM_MESSAGE,
|
||||||
|
KIND_USER_STATUS,
|
||||||
} from "@/shared/constants/kinds";
|
} from "@/shared/constants/kinds";
|
||||||
import type {
|
import type {
|
||||||
RawAcpProviderCatalogEntry,
|
RawAcpProviderCatalogEntry,
|
||||||
@@ -393,6 +394,13 @@ type MockSubscription = {
|
|||||||
kinds: number[] | null;
|
kinds: number[] | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type MockFilter = {
|
||||||
|
"#d"?: string[];
|
||||||
|
"#h"?: string[];
|
||||||
|
authors?: string[];
|
||||||
|
kinds?: number[];
|
||||||
|
};
|
||||||
|
|
||||||
type MockSocket = {
|
type MockSocket = {
|
||||||
handler: WsHandler;
|
handler: WsHandler;
|
||||||
subscriptions: Map<string, MockSubscription>;
|
subscriptions: Map<string, MockSubscription>;
|
||||||
@@ -1230,6 +1238,7 @@ const mockChannels: MockChannel[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const mockMessages = new Map<string, RelayEvent[]>();
|
const mockMessages = new Map<string, RelayEvent[]>();
|
||||||
|
const mockUserStatuses: RelayEvent[] = [];
|
||||||
let mockRelayMembers: RawRelayMember[] = [];
|
let mockRelayMembers: RawRelayMember[] = [];
|
||||||
const mockSockets = new Map<number, MockSocket>();
|
const mockSockets = new Map<number, MockSocket>();
|
||||||
let mockWebsocketSendMutexWedged = false;
|
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) {
|
function hasMockLiveSubscription(channelId: string, kind?: number) {
|
||||||
for (const socket of mockSockets.values()) {
|
for (const socket of mockSockets.values()) {
|
||||||
for (const subscription of socket.subscriptions.values()) {
|
for (const subscription of socket.subscriptions.values()) {
|
||||||
@@ -1954,6 +1974,46 @@ function recordMockMessage(channelId: string, event: RelayEvent) {
|
|||||||
touchMockChannel(channel);
|
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(
|
function emitMockChannelMessage(
|
||||||
channelId: string,
|
channelId: string,
|
||||||
content: string,
|
content: string,
|
||||||
@@ -5022,11 +5082,7 @@ function sendToMockSocket(args: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const filter = rest[1] as {
|
const filter = rest[1] as MockFilter;
|
||||||
"#h"?: string[];
|
|
||||||
kinds?: number[];
|
|
||||||
authors?: string[];
|
|
||||||
};
|
|
||||||
if (filter.kinds?.includes(13534)) {
|
if (filter.kinds?.includes(13534)) {
|
||||||
sendWsText(socket.handler, [
|
sendWsText(socket.handler, [
|
||||||
"EVENT",
|
"EVENT",
|
||||||
@@ -5052,6 +5108,14 @@ function sendToMockSocket(args: {
|
|||||||
return;
|
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];
|
const channelId = filter["#h"]?.[0];
|
||||||
if (!channelId) {
|
if (!channelId) {
|
||||||
sendWsText(socket.handler, ["EOSE", subId]);
|
sendWsText(socket.handler, ["EOSE", subId]);
|
||||||
@@ -5109,6 +5173,26 @@ function sendToMockSocket(args: {
|
|||||||
return;
|
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);
|
const channelId = getChannelIdFromTags(event.tags);
|
||||||
if (!channelId) {
|
if (!channelId) {
|
||||||
sendWsText(socket.handler, [
|
sendWsText(socket.handler, [
|
||||||
@@ -5152,6 +5236,7 @@ export function maybeInstallE2eTauriMocks() {
|
|||||||
resetMockTeams();
|
resetMockTeams();
|
||||||
resetMockWorkflows();
|
resetMockWorkflows();
|
||||||
resetMockMesh();
|
resetMockMesh();
|
||||||
|
resetMockUserStatuses();
|
||||||
mockWebsocketSendMutexWedged = false;
|
mockWebsocketSendMutexWedged = false;
|
||||||
mockWindows("main");
|
mockWindows("main");
|
||||||
window.__SPROUT_E2E_COMMANDS__ = [];
|
window.__SPROUT_E2E_COMMANDS__ = [];
|
||||||
|
|||||||
46
desktop/tests/e2e/profile-custom-emoji-status.spec.ts
Normal file
46
desktop/tests/e2e/profile-custom-emoji-status.spec.ts
Normal file
@@ -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}:`);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user