mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
Fix channel header shared blur layering (#1336)
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
This commit is contained in:
@@ -22,6 +22,7 @@ export default defineConfig({
|
||||
"**/smoke.spec.ts",
|
||||
"**/navigation.spec.ts",
|
||||
"**/channels.spec.ts",
|
||||
"**/channel-shared-header-backdrop.spec.ts",
|
||||
"**/badge.spec.ts",
|
||||
"**/channel-browser.spec.ts",
|
||||
"**/messaging.spec.ts",
|
||||
|
||||
@@ -39,6 +39,7 @@ type AgentSessionThreadPanelProps = {
|
||||
onBackToProfile: () => void;
|
||||
onClose: () => void;
|
||||
widthPx: number;
|
||||
transparentChrome?: boolean;
|
||||
};
|
||||
|
||||
export function AgentSessionThreadPanel({
|
||||
@@ -52,6 +53,7 @@ export function AgentSessionThreadPanel({
|
||||
onBackToProfile,
|
||||
onClose,
|
||||
widthPx,
|
||||
transparentChrome = false,
|
||||
}: AgentSessionThreadPanelProps) {
|
||||
const isLive = isManagedAgentActive(agent);
|
||||
const isOverlay = useIsThreadPanelOverlay();
|
||||
@@ -176,7 +178,9 @@ export function AgentSessionThreadPanel({
|
||||
if (isSplitLayout) {
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<AuxiliaryPanelHeader>{agentHeaderContent}</AuxiliaryPanelHeader>
|
||||
<AuxiliaryPanelHeader transparent={transparentChrome}>
|
||||
{agentHeaderContent}
|
||||
</AuxiliaryPanelHeader>
|
||||
{agentBody}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -17,6 +17,7 @@ type ChannelManagementAuxiliaryPanelProps = {
|
||||
) => void;
|
||||
threadPanelWidthPx: number;
|
||||
useSplitAuxiliaryPane: boolean;
|
||||
transparentChrome?: boolean;
|
||||
};
|
||||
|
||||
export function ChannelManagementAuxiliaryPanel({
|
||||
@@ -30,6 +31,7 @@ export function ChannelManagementAuxiliaryPanel({
|
||||
onThreadPanelResizeStart,
|
||||
threadPanelWidthPx,
|
||||
useSplitAuxiliaryPane,
|
||||
transparentChrome = false,
|
||||
}: ChannelManagementAuxiliaryPanelProps) {
|
||||
const panel = (
|
||||
<ChannelManagementSheet
|
||||
@@ -44,6 +46,7 @@ export function ChannelManagementAuxiliaryPanel({
|
||||
}
|
||||
}}
|
||||
open={true}
|
||||
transparentChrome={transparentChrome}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ type ChannelManagementSheetProps = {
|
||||
onDeleted?: () => void;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
open: boolean;
|
||||
transparentChrome?: boolean;
|
||||
};
|
||||
|
||||
const DEFAULT_EPHEMERAL_TTL_SECONDS = 7 * 24 * 60 * 60;
|
||||
@@ -100,6 +101,7 @@ export function ChannelManagementSheet({
|
||||
onDeleted,
|
||||
onOpenChange,
|
||||
open,
|
||||
transparentChrome = false,
|
||||
}: ChannelManagementSheetProps) {
|
||||
const { isDark } = useTheme();
|
||||
const isSplitLayout = layout === "split";
|
||||
@@ -348,6 +350,7 @@ export function ChannelManagementSheet({
|
||||
isDeleteDialogOpen={isDeleteDialogOpen}
|
||||
isOwner={isOwner}
|
||||
isSplitLayout={isSplitLayout}
|
||||
transparentChrome={transparentChrome}
|
||||
joinChannelMutation={joinChannelMutation}
|
||||
leaveChannelMutation={leaveChannelMutation}
|
||||
memberCount={memberCount}
|
||||
@@ -628,6 +631,7 @@ type ChannelManagementPanelContentProps = {
|
||||
isDeleteDialogOpen: boolean;
|
||||
isOwner: boolean;
|
||||
isSplitLayout: boolean;
|
||||
transparentChrome?: boolean;
|
||||
joinChannelMutation: ChannelMutation;
|
||||
leaveChannelMutation: ChannelMutation;
|
||||
memberCount: number;
|
||||
@@ -659,6 +663,7 @@ function ChannelManagementPanelContent({
|
||||
isDeleteDialogOpen,
|
||||
isOwner,
|
||||
isSplitLayout,
|
||||
transparentChrome = false,
|
||||
joinChannelMutation,
|
||||
leaveChannelMutation,
|
||||
memberCount,
|
||||
@@ -680,7 +685,7 @@ function ChannelManagementPanelContent({
|
||||
return (
|
||||
<>
|
||||
{isSplitLayout ? (
|
||||
<AuxiliaryPanelHeader>
|
||||
<AuxiliaryPanelHeader transparent={transparentChrome}>
|
||||
<AuxiliaryPanelHeaderGroup>
|
||||
{activeView === "canvas" ? (
|
||||
<Button
|
||||
|
||||
@@ -613,6 +613,13 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
}),
|
||||
[agentSessionAgents, openAgentSessionPubkey, profilePanelPubkey, profiles],
|
||||
);
|
||||
const hasSplitAuxiliaryPane =
|
||||
useSplitAuxiliaryPane &&
|
||||
(channelManagementOpen ||
|
||||
Boolean(threadHeadMessage) ||
|
||||
shouldShowThreadSkeleton ||
|
||||
Boolean(activeChannel && selectedAgent) ||
|
||||
Boolean(profilePanelPubkey));
|
||||
const wrapAux = (panel: React.ReactNode, testId: string) =>
|
||||
useSplitAuxiliaryPane ? (
|
||||
<RightAuxiliaryPane
|
||||
@@ -628,7 +635,18 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
panel
|
||||
);
|
||||
return (
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-row overflow-hidden">
|
||||
<div className="relative flex min-h-0 min-w-0 flex-1 flex-row overflow-hidden">
|
||||
{!isSinglePanelView ? (
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
"pointer-events-none absolute inset-x-0 top-0 z-30 bg-background/80 backdrop-blur-md supports-backdrop-filter:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-backdrop-filter:bg-background/55",
|
||||
channelChrome.headerHeight,
|
||||
)}
|
||||
data-testid="channel-shared-header-backdrop"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{!isSinglePanelView ? (
|
||||
<section
|
||||
aria-label="Channel messages and composer"
|
||||
@@ -829,6 +847,7 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
onThreadPanelResizeStart={onThreadPanelResizeStart}
|
||||
threadPanelWidthPx={threadPanelWidthPx}
|
||||
useSplitAuxiliaryPane={useSplitAuxiliaryPane}
|
||||
transparentChrome={hasSplitAuxiliaryPane}
|
||||
/>
|
||||
) : threadHeadMessage ? (
|
||||
(() => {
|
||||
@@ -849,6 +868,7 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
useSplitAuxiliaryPane ? false : isSinglePanelView
|
||||
}
|
||||
layout={useSplitAuxiliaryPane ? "split" : "standalone"}
|
||||
transparentChrome={useSplitAuxiliaryPane}
|
||||
onCancelEdit={onCancelEdit}
|
||||
onCancelReply={onCancelThreadReply}
|
||||
onClose={onCloseThread}
|
||||
@@ -900,6 +920,7 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
useSplitAuxiliaryPane ? false : isSinglePanelView
|
||||
}
|
||||
layout={useSplitAuxiliaryPane ? "split" : "standalone"}
|
||||
transparentChrome={useSplitAuxiliaryPane}
|
||||
onClose={onCloseThread}
|
||||
widthPx={threadPanelWidthPx}
|
||||
/>
|
||||
@@ -929,6 +950,7 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
useSplitAuxiliaryPane ? false : isSinglePanelView
|
||||
}
|
||||
layout={useSplitAuxiliaryPane ? "split" : "standalone"}
|
||||
transparentChrome={useSplitAuxiliaryPane}
|
||||
profiles={profiles}
|
||||
onBackToProfile={() => onOpenProfilePanel(selectedAgent.pubkey)}
|
||||
onClose={onCloseAgentSession}
|
||||
@@ -946,6 +968,7 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
useSplitAuxiliaryPane ? false : isSinglePanelView
|
||||
}
|
||||
layout={useSplitAuxiliaryPane ? "split" : "standalone"}
|
||||
transparentChrome={useSplitAuxiliaryPane}
|
||||
onClose={onCloseProfilePanel}
|
||||
onOpenDm={onOpenDm}
|
||||
onOpenProfile={onOpenProfilePanel}
|
||||
|
||||
@@ -725,6 +725,7 @@ export function ChannelScreen({
|
||||
}}
|
||||
onToggleMembers={() => setIsMembersSidebarOpen((prev) => !prev)}
|
||||
showHeaderContent={!isSinglePanelView}
|
||||
transparentChrome={activeChannel?.channelType !== "forum"}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ type ChannelScreenHeaderProps = {
|
||||
isAddBotOpen?: boolean;
|
||||
isJoining?: boolean;
|
||||
showHeaderContent?: boolean;
|
||||
transparentChrome?: boolean;
|
||||
onAddBotOpenChange?: (open: boolean) => void;
|
||||
onJoinChannel?: () => Promise<void>;
|
||||
onManageChannel: () => void;
|
||||
@@ -46,6 +47,7 @@ export function ChannelScreenHeader({
|
||||
isJoining = false,
|
||||
onAddBotOpenChange,
|
||||
showHeaderContent = true,
|
||||
transparentChrome = false,
|
||||
onJoinChannel,
|
||||
onManageChannel,
|
||||
onToggleMembers,
|
||||
@@ -120,6 +122,7 @@ export function ChannelScreenHeader({
|
||||
/>
|
||||
}
|
||||
title={activeChannelTitle}
|
||||
transparentChrome={transparentChrome}
|
||||
visibility={activeChannel?.visibility}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,6 @@ import type * as React from "react";
|
||||
|
||||
import { THREAD_PANEL_MIN_WIDTH_PX } from "@/shared/hooks/useThreadPanelWidth";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import { PANEL_ENTER_MOTION_CLASS } from "@/shared/ui/OverlayPanelBackdrop";
|
||||
|
||||
type RightAuxiliaryPaneProps = {
|
||||
canResetWidth: boolean;
|
||||
@@ -26,8 +25,7 @@ export function RightAuxiliaryPane({
|
||||
return (
|
||||
<aside
|
||||
className={cn(
|
||||
"group/right-pane relative flex h-full shrink-0 flex-col overflow-hidden bg-background before:pointer-events-none before:absolute before:bottom-0 before:left-0 before:top-0 before:z-40 before:w-px before:bg-border/80 before:content-['']",
|
||||
PANEL_ENTER_MOTION_CLASS,
|
||||
"group/right-pane relative flex h-full shrink-0 flex-col overflow-hidden bg-background before:pointer-events-none before:absolute before:bottom-0 before:left-0 before:top-0 before:z-50 before:w-px before:bg-border/80 before:content-['']",
|
||||
)}
|
||||
data-testid={testId}
|
||||
style={{
|
||||
@@ -39,7 +37,7 @@ export function RightAuxiliaryPane({
|
||||
>
|
||||
<button
|
||||
aria-label="Resize panel"
|
||||
className="peer/right-pane-resize group/right-pane-resize absolute inset-y-0 left-0 z-40 w-3 -translate-x-1/2 cursor-col-resize"
|
||||
className="peer/right-pane-resize group/right-pane-resize absolute inset-y-0 left-0 z-50 w-3 -translate-x-1/2 cursor-col-resize"
|
||||
data-testid="right-auxiliary-pane-resize-handle"
|
||||
onDoubleClick={canResetWidth ? onResetWidth : undefined}
|
||||
onPointerDown={onResizeStart}
|
||||
|
||||
@@ -32,6 +32,8 @@ type ChatHeaderProps = {
|
||||
mode?: "home" | "channel" | "agents" | "workflows" | "pulse" | "projects";
|
||||
overlaysContent?: boolean;
|
||||
statusBadge?: React.ReactNode;
|
||||
/** Render the chrome wrapper without an individual backdrop when a parent supplies shared blur. */
|
||||
transparentChrome?: boolean;
|
||||
};
|
||||
|
||||
const HEADER_ICON_CLASS = "h-4 w-4 text-muted-foreground";
|
||||
@@ -93,6 +95,7 @@ export function ChatHeader({
|
||||
mode = "channel",
|
||||
overlaysContent = false,
|
||||
statusBadge,
|
||||
transparentChrome = false,
|
||||
}: ChatHeaderProps) {
|
||||
const trimmedDescription = description?.trim() ?? "";
|
||||
|
||||
@@ -171,7 +174,10 @@ export function ChatHeader({
|
||||
<div
|
||||
ref={chromeWrapperRef}
|
||||
className={cn(
|
||||
"pointer-events-none relative z-30 overflow-hidden rounded-tl-xl bg-background/80 backdrop-blur-md supports-backdrop-filter:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-backdrop-filter:bg-background/55",
|
||||
"pointer-events-none relative z-40 overflow-visible rounded-tl-xl",
|
||||
transparentChrome
|
||||
? "bg-transparent"
|
||||
: "bg-background/80 backdrop-blur-md supports-backdrop-filter:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-backdrop-filter:bg-background/55",
|
||||
channelChrome.negativeMargin,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -88,6 +88,7 @@ type MessageThreadPanelProps = {
|
||||
threadHeadVideoReviewContext?: VideoReviewContext;
|
||||
toolbarExtraActions?: React.ReactNode;
|
||||
widthPx: number;
|
||||
transparentChrome?: boolean;
|
||||
isFollowingThread?: boolean;
|
||||
isMessageUnreadById?: (messageId: string) => boolean;
|
||||
onFollowThread?: () => void;
|
||||
@@ -103,6 +104,7 @@ type MessageThreadPanelSkeletonProps = {
|
||||
layout?: "standalone" | "split";
|
||||
onClose: () => void;
|
||||
widthPx: number;
|
||||
transparentChrome?: boolean;
|
||||
};
|
||||
|
||||
function canManageMessage(
|
||||
@@ -225,6 +227,7 @@ export function MessageThreadPanelSkeleton({
|
||||
layout = "standalone",
|
||||
onClose,
|
||||
widthPx,
|
||||
transparentChrome = false,
|
||||
}: MessageThreadPanelSkeletonProps) {
|
||||
const isOverlay = useIsThreadPanelOverlay();
|
||||
const isFloatingOverlay = isOverlay && !isSinglePanelView;
|
||||
@@ -296,7 +299,9 @@ export function MessageThreadPanelSkeleton({
|
||||
if (isSplitLayout) {
|
||||
return (
|
||||
<div className="relative flex min-h-0 flex-1 flex-col">
|
||||
<AuxiliaryPanelHeader>{threadHeaderContent}</AuxiliaryPanelHeader>
|
||||
<AuxiliaryPanelHeader transparent={transparentChrome}>
|
||||
{threadHeaderContent}
|
||||
</AuxiliaryPanelHeader>
|
||||
{threadBody}
|
||||
<ThreadComposerSkeleton />
|
||||
</div>
|
||||
@@ -379,6 +384,7 @@ export function MessageThreadPanel({
|
||||
threadTypingPubkeys,
|
||||
toolbarExtraActions,
|
||||
widthPx,
|
||||
transparentChrome = false,
|
||||
}: MessageThreadPanelProps) {
|
||||
const threadBodyRef = React.useRef<HTMLDivElement>(null);
|
||||
const threadContentRef = React.useRef<HTMLDivElement>(null);
|
||||
@@ -943,7 +949,9 @@ export function MessageThreadPanel({
|
||||
if (isSplitLayout) {
|
||||
return (
|
||||
<div className="relative flex min-h-0 flex-1 flex-col">
|
||||
<AuxiliaryPanelHeader>{threadHeaderContent}</AuxiliaryPanelHeader>
|
||||
<AuxiliaryPanelHeader transparent={transparentChrome}>
|
||||
{threadHeaderContent}
|
||||
</AuxiliaryPanelHeader>
|
||||
{threadScrollRegion}
|
||||
{threadFooter}
|
||||
</div>
|
||||
|
||||
@@ -95,7 +95,6 @@ import type {
|
||||
} from "@/shared/api/types";
|
||||
import { UserProfilePanelFrame } from "@/features/profile/ui/UserProfilePanelFrame";
|
||||
import { getUserProfilePanelHeaderContent } from "@/features/profile/ui/UserProfilePanelHeaderContent";
|
||||
|
||||
export type { ProfilePanelTab, ProfilePanelView };
|
||||
|
||||
export function UserProfilePanel({
|
||||
@@ -116,6 +115,7 @@ export function UserProfilePanel({
|
||||
tab: controlledTab,
|
||||
view: controlledView,
|
||||
widthPx,
|
||||
transparentChrome = false,
|
||||
}: UserProfilePanelProps) {
|
||||
const isOverlay = useIsThreadPanelOverlay();
|
||||
const isFloatingOverlay = isOverlay && !isSinglePanelView;
|
||||
@@ -993,6 +993,7 @@ export function UserProfilePanel({
|
||||
profileBody={profileBody}
|
||||
splitPaneClamp={splitPaneClamp}
|
||||
widthPx={widthPx}
|
||||
transparentChrome={transparentChrome}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ type UserProfilePanelFrameProps = {
|
||||
profileBody: React.ReactNode;
|
||||
splitPaneClamp: boolean;
|
||||
widthPx: number;
|
||||
transparentChrome?: boolean;
|
||||
};
|
||||
|
||||
export function UserProfilePanelFrame({
|
||||
@@ -46,12 +47,13 @@ export function UserProfilePanelFrame({
|
||||
profileBody,
|
||||
splitPaneClamp,
|
||||
widthPx,
|
||||
transparentChrome = false,
|
||||
}: UserProfilePanelFrameProps) {
|
||||
if (isSplitLayout) {
|
||||
return (
|
||||
<>
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<AuxiliaryPanelHeader>
|
||||
<AuxiliaryPanelHeader transparent={transparentChrome}>
|
||||
{headerLeftContent}
|
||||
{headerActions}
|
||||
</AuxiliaryPanelHeader>
|
||||
|
||||
@@ -110,6 +110,7 @@ export type UserProfilePanelProps = {
|
||||
tab?: ProfilePanelTab;
|
||||
view?: ProfilePanelView;
|
||||
widthPx: number;
|
||||
transparentChrome?: boolean;
|
||||
};
|
||||
|
||||
export function deriveProfileChannels(
|
||||
|
||||
@@ -3,7 +3,10 @@ import type * as React from "react";
|
||||
import { channelChrome } from "@/shared/layout/chromeLayout";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
|
||||
type AuxiliaryPanelHeaderProps = React.ComponentProps<"div">;
|
||||
type AuxiliaryPanelHeaderProps = React.ComponentProps<"div"> & {
|
||||
/** Render header content without its own backdrop for a shared parent chrome. */
|
||||
transparent?: boolean;
|
||||
};
|
||||
type AuxiliaryPanelHeaderGroupProps = React.ComponentProps<"div">;
|
||||
type AuxiliaryPanelTitleProps = React.ComponentProps<"h2">;
|
||||
|
||||
@@ -11,19 +14,23 @@ type AuxiliaryPanelTitleProps = React.ComponentProps<"h2">;
|
||||
export function AuxiliaryPanelHeader({
|
||||
className,
|
||||
children,
|
||||
transparent = false,
|
||||
...props
|
||||
}: AuxiliaryPanelHeaderProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"pointer-events-none relative z-30 bg-background/80 backdrop-blur-md supports-backdrop-filter:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-backdrop-filter:bg-background/55",
|
||||
"pointer-events-none relative z-40 overflow-visible",
|
||||
transparent
|
||||
? "bg-transparent"
|
||||
: "bg-background/80 backdrop-blur-md supports-backdrop-filter:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-backdrop-filter:bg-background/55",
|
||||
channelChrome.negativeMargin,
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className="pointer-events-auto relative z-30 shrink-0 cursor-default select-none px-5 py-2"
|
||||
className="pointer-events-auto relative z-40 shrink-0 cursor-default select-none px-5 py-2"
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<div className="flex h-9 min-w-0 items-center gap-2.5">{children}</div>
|
||||
|
||||
134
desktop/tests/e2e/channel-shared-header-backdrop.spec.ts
Normal file
134
desktop/tests/e2e/channel-shared-header-backdrop.spec.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { waitForAnimations } from "../helpers/animations";
|
||||
import { installMockBridge } from "../helpers/bridge";
|
||||
|
||||
type MockMessageWindow = Window & {
|
||||
__BUZZ_E2E_EMIT_MOCK_MESSAGE__?: (input: {
|
||||
channelName: string;
|
||||
content: string;
|
||||
parentEventId?: string | null;
|
||||
pubkey?: string;
|
||||
}) => { id: string } | undefined;
|
||||
__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?: (input: {
|
||||
channelName: string;
|
||||
}) => boolean;
|
||||
};
|
||||
|
||||
const CHANNEL_NAME = "engineering";
|
||||
const MOCK_IDENTITY_PUBKEY = "deadbeef".repeat(8);
|
||||
const ALICE_PUBKEY =
|
||||
"953d3363262e86b770419834c53d2446409db6d918a57f8f339d495d54ab001f";
|
||||
|
||||
async function waitForMockLiveSubscription(
|
||||
page: import("@playwright/test").Page,
|
||||
channelName: string,
|
||||
) {
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return page.evaluate((name) => {
|
||||
return (
|
||||
(
|
||||
window as MockMessageWindow
|
||||
).__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({ channelName: name }) ??
|
||||
false
|
||||
);
|
||||
}, channelName);
|
||||
})
|
||||
.toBe(true);
|
||||
}
|
||||
|
||||
test.describe("channel shared header backdrop", () => {
|
||||
test.use({ viewport: { width: 1280, height: 720 } });
|
||||
|
||||
test("spans channel and split auxiliary columns with one backdrop", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page);
|
||||
await page.goto("/");
|
||||
await page.getByTestId(`channel-${CHANNEL_NAME}`).click();
|
||||
await expect(page.getByTestId("chat-title")).toHaveText(CHANNEL_NAME);
|
||||
await waitForMockLiveSubscription(page, CHANNEL_NAME);
|
||||
|
||||
const rootId = await page.evaluate(
|
||||
({ channelName, pubkey }) =>
|
||||
(window as MockMessageWindow).__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
|
||||
channelName,
|
||||
content: "Root message for shared header backdrop coverage.",
|
||||
pubkey,
|
||||
})?.id ?? null,
|
||||
{ channelName: CHANNEL_NAME, pubkey: MOCK_IDENTITY_PUBKEY },
|
||||
);
|
||||
expect(rootId).not.toBeNull();
|
||||
|
||||
await page.evaluate(
|
||||
({ channelName, parentEventId, pubkey }) => {
|
||||
(window as MockMessageWindow).__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
|
||||
channelName,
|
||||
content: "Reply that opens a split thread panel.",
|
||||
parentEventId,
|
||||
pubkey,
|
||||
});
|
||||
},
|
||||
{
|
||||
channelName: CHANNEL_NAME,
|
||||
parentEventId: rootId,
|
||||
pubkey: ALICE_PUBKEY,
|
||||
},
|
||||
);
|
||||
|
||||
const replyButton = page.locator('[data-testid^="reply-message-"]').first();
|
||||
await expect(replyButton).toBeVisible();
|
||||
await replyButton.click({ force: true });
|
||||
await expect(page.getByTestId("message-thread-panel")).toBeVisible();
|
||||
|
||||
const sharedBackdrop = page.getByTestId("channel-shared-header-backdrop");
|
||||
await expect(sharedBackdrop).toHaveCount(1);
|
||||
|
||||
const chatHeader = page.getByTestId("chat-header");
|
||||
const auxiliaryResizeHandle = page.getByTestId(
|
||||
"right-auxiliary-pane-resize-handle",
|
||||
);
|
||||
|
||||
const [
|
||||
hostBox,
|
||||
backdropBox,
|
||||
backdropFilter,
|
||||
backdropZIndex,
|
||||
headerZIndex,
|
||||
resizeHandleZIndex,
|
||||
auxiliaryPaneAnimationName,
|
||||
] = await Promise.all([
|
||||
page.getByTestId("channel-drop-zone").locator("..").boundingBox(),
|
||||
sharedBackdrop.boundingBox(),
|
||||
sharedBackdrop.evaluate(
|
||||
(element) => getComputedStyle(element).backdropFilter,
|
||||
),
|
||||
sharedBackdrop.evaluate((element) =>
|
||||
Number(getComputedStyle(element).zIndex),
|
||||
),
|
||||
chatHeader.evaluate((element) =>
|
||||
Number(getComputedStyle(element.parentElement ?? element).zIndex),
|
||||
),
|
||||
auxiliaryResizeHandle.evaluate((element) =>
|
||||
Number(getComputedStyle(element).zIndex),
|
||||
),
|
||||
page
|
||||
.getByTestId("message-thread-panel")
|
||||
.evaluate((element) => getComputedStyle(element).animationName),
|
||||
]);
|
||||
|
||||
expect(hostBox).not.toBeNull();
|
||||
expect(backdropBox).not.toBeNull();
|
||||
expect(Math.round(backdropBox?.x ?? 0)).toBe(Math.round(hostBox?.x ?? 0));
|
||||
expect(Math.round(backdropBox?.width ?? 0)).toBe(
|
||||
Math.round(hostBox?.width ?? 0),
|
||||
);
|
||||
expect(backdropFilter).not.toBe("none");
|
||||
expect(headerZIndex).toBeGreaterThan(backdropZIndex);
|
||||
expect(resizeHandleZIndex).toBeGreaterThan(backdropZIndex);
|
||||
expect(auxiliaryPaneAnimationName).toBe("none");
|
||||
|
||||
await waitForAnimations(page);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user