Files
buzz/desktop/tests/e2e/buzz-theme-screenshots.spec.ts
0571f5455b Polish glass Huddle tray behavior (#5590)
## Summary

- inset the in-app Huddle tray with four rounded corners and even 8px
spacing when Glass background is enabled
- keep the popped-out Huddle dock full-width
- hide and suppress Glass background on Linux

## Why

The in-app tray reused the opaque backing needed by non-glass windows,
which covered the native vibrancy around it. Linux does not support this
window treatment.

## Testing

- `pnpm -C desktop build:e2e`
- focused Appearance and Huddle Playwright smoke tests
- pre-push desktop checks, typecheck, and 4,666 unit tests

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
2026-08-13 15:40:50 -07:00

1362 lines
49 KiB
TypeScript

import { expect, test, type Page } from "@playwright/test";
import { waitForAnimations } from "../helpers/animations";
import { installMockBridge } from "../helpers/bridge";
const SHOTS = "test-results/buzz-theme";
const THEME_STORAGE_KEY = "buzz-theme";
const GLASS_BACKGROUND_STORAGE_KEY = "buzz-glass-background";
const GLASS_OPACITY_STORAGE_KEY = "buzz-glass-opacity";
const PROMINENT_ACTIVE_TAB_STORAGE_KEY = "buzz-prominent-active-tab";
const MOCK_PUBKEY = "deadbeef".repeat(8);
const GENERAL_CHANNEL_ID = "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50";
/**
* Seed the active theme into localStorage BEFORE the mock bridge installs so
* ThemeProvider reads it on first mount (init scripts run in registration
* order; React reads state on mount, which the bridge triggers).
*/
async function seedTheme(page: Page, theme: string) {
await page.addInitScript(
({ key, value }) => {
window.localStorage.setItem(key, value);
},
{ key: THEME_STORAGE_KEY, value: theme },
);
}
async function seedIconChannelSection(page: Page) {
await page.addInitScript(
({ channelId, pubkey }) => {
window.localStorage.setItem(
`buzz-channel-sections.v1:${pubkey}`,
JSON.stringify({
version: 1,
sections: [
{
id: "alignment-section",
name: "Team channels",
icon: "📌",
order: 0,
},
],
assignments: { [channelId]: "alignment-section" },
}),
);
},
{ channelId: GENERAL_CHANNEL_ID, pubkey: MOCK_PUBKEY },
);
}
async function openChannel(page: Page) {
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByTestId("channel-general").click();
await expect(page.getByTestId("chat-title")).toHaveText("general");
await expect(page.getByTestId("app-sidebar")).toBeVisible();
}
async function expectBuzzSidebarPalette(page: Page, mode: "light" | "dark") {
const mutedColor =
mode === "light" ? "rgba(0, 0, 0, 0.4)" : "rgba(255, 255, 255, 0.4)";
const searchSurface =
mode === "light" ? "rgba(0, 0, 0, 0.04)" : "rgba(255, 255, 255, 0.04)";
const rowHoverSurface =
mode === "light" ? "rgba(0, 0, 0, 0.04)" : "rgba(255, 255, 255, 0.04)";
const activeSurface =
mode === "light" ? "rgba(0, 0, 0, 0.07)" : "rgba(255, 255, 255, 0.16)";
const chromeColor =
mode === "light" ? "rgba(0, 0, 0, 0.5)" : "rgba(255, 255, 255, 0.5)";
const search = page.getByTestId("open-search");
const pinnedHeader = page.getByTestId("sidebar-pinned-header");
const sidebarScroller = page.locator(".buzz-sidebar-scrollbar");
const scrollContent = page.getByTestId("sidebar-scroll-content");
const primaryMenu = page.getByTestId("sidebar-primary-menu");
const sectionLabel = page
.locator('[data-sidebar="group-label"]')
.filter({ hasText: "Channels" })
.first();
await expect(sectionLabel).toHaveCSS("color", mutedColor);
await expect(search).toHaveCSS("background-color", searchSurface);
await expect(search.locator("svg").first()).toHaveCSS("color", mutedColor);
await expect(search.locator("span").first()).toHaveCSS("color", mutedColor);
await expect(pinnedHeader).toHaveCSS("padding-bottom", "8px");
await expect(pinnedHeader).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
await expect(pinnedHeader).toHaveCSS("margin-left", "3px");
await expect(pinnedHeader).toHaveCSS("margin-right", "3px");
await expect(pinnedHeader).toHaveCSS("padding-right", "8px");
await expect(sidebarScroller).toHaveCSS("padding-left", "0px");
await expect(sidebarScroller).toHaveCSS("padding-right", "0px");
await expect(scrollContent).toHaveCSS("padding-left", "3px");
await expect(scrollContent).toHaveCSS("padding-right", "3px");
const pinnedSpacerColor = await pinnedHeader.evaluate(
(element) => getComputedStyle(element, "::before").backgroundColor,
);
expect(pinnedSpacerColor).toBe("rgba(0, 0, 0, 0)");
await expect(sidebarScroller.getByTestId("open-agents-view")).toBeVisible();
const searchBox = await search.boundingBox();
const pinnedHeaderBox = await pinnedHeader.boundingBox();
const primaryMenuBox = await primaryMenu.boundingBox();
const primaryRowBox = await page
.getByTestId("open-agents-view")
.boundingBox();
const activeRowBox = await page.getByTestId("channel-general").boundingBox();
const hoverRowBox = await page.getByTestId("channel-random").boundingBox();
const scrollContentBox = await scrollContent.evaluate((element) => {
const box = element.getBoundingClientRect();
return { left: box.left, right: box.right };
});
expect(searchBox).not.toBeNull();
expect(pinnedHeaderBox).not.toBeNull();
expect(primaryMenuBox).not.toBeNull();
expect(primaryRowBox).not.toBeNull();
expect(activeRowBox).not.toBeNull();
expect(hoverRowBox).not.toBeNull();
if (
!searchBox ||
!pinnedHeaderBox ||
!primaryMenuBox ||
!primaryRowBox ||
!activeRowBox ||
!hoverRowBox
) {
throw new Error("Sidebar search or primary navigation geometry is missing");
}
expect(primaryMenuBox.y - (searchBox.y + searchBox.height)).toBe(8);
expect(
pinnedHeaderBox.y +
pinnedHeaderBox.height -
(searchBox.y + searchBox.height),
).toBe(8);
expect(primaryMenuBox.y - (pinnedHeaderBox.y + pinnedHeaderBox.height)).toBe(
0,
);
for (const rowBox of [primaryRowBox, activeRowBox, hoverRowBox]) {
expect(Math.abs(rowBox.x - searchBox.x)).toBeLessThanOrEqual(0.5);
// Linux CI reserves a classic scrollbar gutter while macOS uses an
// overlay scrollbar. Compare each row to its usable scroll area so the
// alignment check remains platform-independent.
const rowLeftSpacing = rowBox.x - scrollContentBox.left;
const rowRightSpacing = scrollContentBox.right - (rowBox.x + rowBox.width);
expect(Math.abs(rowLeftSpacing - rowRightSpacing)).toBeLessThanOrEqual(0.5);
}
await expect(page.locator("[data-buzz-sidebar-secondary]").first()).toHaveCSS(
"color",
mutedColor,
);
await expect(page.locator('[data-sidebar="trigger"]')).toHaveCSS(
"color",
chromeColor,
);
await expect(page.getByTestId("global-back")).toHaveCSS("color", chromeColor);
await expect(page.getByTestId("global-forward")).toHaveCSS(
"color",
chromeColor,
);
await expect(page.getByTestId("channel-general")).not.toHaveCSS(
"color",
mutedColor,
);
await expect(page.getByTestId("channel-general")).toHaveCSS(
"font-weight",
"400",
);
await expect(
page.getByTestId("channel-general").locator("[data-sidebar-row-label]"),
).toHaveCSS("opacity", "1");
await page.mouse.move(600, 100);
await expect(page.getByTestId("channel-general")).toHaveCSS(
"background-color",
activeSurface,
);
const hoverChannel = page.getByTestId("channel-random");
await hoverChannel.hover();
await expect(hoverChannel).toHaveCSS("background-color", rowHoverSurface);
const firstDmItem = page
.getByTestId("dm-list")
.locator('[data-sidebar="menu-item"]')
.first();
const firstDmButton = firstDmItem.locator('[data-sidebar="menu-button"]');
const firstDmLabel = firstDmButton.locator("[data-sidebar-row-label]");
const closeDmButton = firstDmItem.getByRole("button", {
name: "Close direct message",
});
const hoverChannelLabel = hoverChannel.locator("[data-sidebar-row-label]");
const hoverChannelIcon = hoverChannel.locator("svg").first();
const agentsButton = page.getByTestId("open-agents-view");
const agentsLabel = agentsButton.locator('[data-sidebar="menu-label"]');
const agentsIcon = agentsButton.locator("svg").first();
const sidebarForeground = await page
.getByTestId("app-sidebar")
.evaluate((element) => getComputedStyle(element).color);
await expect(hoverChannel).toHaveCSS("color", sidebarForeground);
await expect(firstDmButton).toHaveCSS("color", sidebarForeground);
await expect(agentsButton).toHaveCSS("color", sidebarForeground);
await expect(hoverChannelLabel).toHaveCSS("opacity", "0.8");
await expect(hoverChannelIcon).toHaveCSS("opacity", "0.8");
await expect(firstDmButton).toHaveCSS("opacity", "1");
await expect(firstDmLabel).toHaveCSS("opacity", "0.8");
await expect(agentsLabel).toHaveCSS("opacity", "0.8");
await expect(agentsIcon).toHaveCSS("opacity", "0.8");
await firstDmItem.hover();
await expect(closeDmButton).toBeVisible();
await closeDmButton.hover();
await expect(firstDmButton).toHaveCSS("background-color", rowHoverSurface);
const scrollbarThumbColor = await sidebarScroller.evaluate(
(element) =>
getComputedStyle(element, "::-webkit-scrollbar-thumb").backgroundColor,
);
expect(scrollbarThumbColor).toBe(searchSurface);
}
async function expectIconlessSectionTitleAligned(
page: Page,
listTestId: "stream-list" | "dm-list",
) {
const titleBox = await page
.getByTestId(`${listTestId}-section-label`)
.locator("[data-sidebar-section-title]")
.boundingBox();
const firstRowIconX = await page
.getByTestId(listTestId)
.locator('[data-sidebar="menu-button"]')
.first()
.evaluate((element) => {
const box = element.getBoundingClientRect();
const paddingLeft = Number.parseFloat(
getComputedStyle(element).paddingLeft,
);
return box.x + paddingLeft;
});
expect(titleBox).not.toBeNull();
if (!titleBox) {
throw new Error(`Sidebar section ${listTestId} is missing label geometry`);
}
expect(Math.abs(titleBox.x - firstRowIconX)).toBeLessThanOrEqual(0.5);
}
async function expectBuzzContentShadow(page: Page, mode: "light" | "dark") {
const effects = await page.evaluate(() => {
const shell = document.querySelector(".buzz-huddle-shell");
const content = document.querySelector("[data-buzz-content-surface]");
const shadowViewport = document.querySelector(
"[data-buzz-shadow-viewport]",
);
return {
appStroke: shell ? getComputedStyle(shell, "::before").boxShadow : "",
contentShadow: content ? getComputedStyle(content).boxShadow : "",
shadowViewportOverflow: shadowViewport
? getComputedStyle(shadowViewport).overflow
: "",
};
});
expect(effects.appStroke).toBe("none");
if (mode === "light") {
expect(effects.contentShadow).toContain("4px");
expect(effects.contentShadow).toContain("rgba(0, 0, 0, 0.07)");
expect(effects.shadowViewportOverflow).toBe("visible");
} else {
expect(effects.contentShadow).not.toContain("4px");
expect(effects.contentShadow).not.toContain("rgba(255, 255, 255, 0.07)");
expect(effects.shadowViewportOverflow).toBe("hidden");
}
}
async function expectBuzzGradientPaint(
page: Page,
mode: "light" | "dark",
): Promise<string> {
const paint = await page.evaluate(() => {
const root = document.documentElement;
const appSurface = document.querySelector(".buzz-huddle-app-surface");
const lightLayer = document.querySelector('[data-buzz-gradient="light"]');
const darkLayer = document.querySelector('[data-buzz-gradient="dark"]');
const sidebarRoot = document.querySelector(
'[data-testid="app-sidebar"], [data-testid="settings-sidebar"]',
);
const sidebarSurface =
sidebarRoot?.querySelector('[data-sidebar="sidebar"]') ?? sidebarRoot;
const appStyles = appSurface ? getComputedStyle(appSurface) : null;
const lightStyles = lightLayer ? getComputedStyle(lightLayer) : null;
const darkStyles = darkLayer ? getComputedStyle(darkLayer) : null;
return {
isDark: root.classList.contains("dark"),
theme: root.getAttribute("data-buzz-theme"),
surfaceImage: appStyles?.backgroundImage ?? "",
lightImage: lightStyles?.backgroundImage ?? "",
lightOpacity: lightStyles?.opacity ?? "",
darkImage: darkStyles?.backgroundImage ?? "",
darkOpacity: darkStyles?.opacity ?? "",
sidebarImage: sidebarSurface
? getComputedStyle(sidebarSurface).backgroundImage
: "",
};
});
expect(paint.theme).toBe(mode === "light" ? "buzz" : "buzz-dark");
expect(paint.isDark).toBe(mode === "dark");
expect(paint.surfaceImage).toBe("none");
expect(paint.lightImage).not.toBe("");
expect(paint.lightImage).not.toBe("none");
expect(paint.darkImage).not.toBe("");
expect(paint.darkImage).not.toBe("none");
expect(paint.lightImage).not.toBe(paint.darkImage);
expect(paint.lightOpacity).toBe(mode === "light" ? "1" : "0");
expect(paint.darkOpacity).toBe(mode === "dark" ? "1" : "0");
expect(paint.sidebarImage).toBe("none");
return mode === "light" ? paint.lightImage : paint.darkImage;
}
async function expectBuzzSettingsPalette(page: Page, mode: "light" | "dark") {
const mutedColor =
mode === "light" ? "rgba(0, 0, 0, 0.4)" : "rgba(255, 255, 255, 0.4)";
const sidebar = page.getByTestId("settings-sidebar");
const sectionLabel = sidebar
.locator('[data-sidebar="group-label"]')
.filter({ hasText: "Personal" });
await expect(sectionLabel).toHaveCSS("color", mutedColor);
await expect(page.getByTestId("settings-nav-profile")).not.toHaveCSS(
"color",
mutedColor,
);
await expectBuzzGradientPaint(page, mode);
const version = page.getByTestId("settings-version");
if ((await version.count()) > 0) {
await expect(version).toHaveCSS("color", mutedColor);
}
}
async function expectAppliedBuzzTheme(
page: Page,
themeName: "buzz" | "buzz-dark",
storedTheme: "buzz" | "buzz-dark" = themeName,
) {
const isDark = themeName === "buzz-dark";
await expect
.poll(() =>
page.evaluate((storageKey) => {
const root = document.documentElement;
const styles = getComputedStyle(root);
return {
storedTheme: window.localStorage.getItem(storageKey),
isDark: root.classList.contains("dark"),
buzzTheme: root.getAttribute("data-buzz-theme"),
gradientTop: styles.getPropertyValue("--buzz-gradient-top").trim(),
gradientBottom: styles
.getPropertyValue("--buzz-gradient-bottom")
.trim(),
};
}, THEME_STORAGE_KEY),
)
.toEqual({
storedTheme,
isDark,
buzzTheme: themeName,
gradientTop: isDark ? "#4a4616" : "#e6e6b6",
gradientBottom: isDark ? "#0a1423" : "#c4d0da",
});
}
async function emitNativeThemeChange(page: Page, theme: "light" | "dark") {
await page.evaluate(async (nextTheme) => {
const tauriWindow = window as typeof window & {
__TAURI_INTERNALS__?: {
invoke?: (
command: string,
payload?: Record<string, unknown>,
) => Promise<unknown>;
};
};
const invoke = tauriWindow.__TAURI_INTERNALS__?.invoke;
if (!invoke) throw new Error("Mock Tauri invoke bridge is unavailable.");
await invoke("plugin:event|emit", {
event: "tauri://theme-changed",
payload: nextTheme,
});
}, theme);
}
test("buzz light sidebar gradient", async ({ page }) => {
await seedTheme(page, "buzz");
await installMockBridge(page);
await openChannel(page);
await expectBuzzGradientPaint(page, "light");
await expectBuzzSidebarPalette(page, "light");
await expectBuzzContentShadow(page, "light");
await expectIconlessSectionTitleAligned(page, "stream-list");
await expectIconlessSectionTitleAligned(page, "dm-list");
await waitForAnimations(page);
await page
.getByTestId("app-sidebar")
.screenshot({ path: `${SHOTS}/01-buzz-light-sidebar.png` });
});
test("buzz dark sidebar gradient", async ({ page }) => {
await seedTheme(page, "buzz-dark");
await installMockBridge(page);
await openChannel(page);
await expectBuzzGradientPaint(page, "dark");
await expectBuzzSidebarPalette(page, "dark");
await expectBuzzContentShadow(page, "dark");
await expectIconlessSectionTitleAligned(page, "stream-list");
await expectIconlessSectionTitleAligned(page, "dm-list");
await expect(page.locator("[data-buzz-content-surface]")).toHaveCSS(
"background-color",
"rgb(26, 26, 26)",
);
await waitForAnimations(page);
await page
.getByTestId("app-sidebar")
.screenshot({ path: `${SHOTS}/02-buzz-dark-sidebar.png` });
});
test("custom section icon and name align with channel columns", async ({
page,
}) => {
await seedTheme(page, "buzz");
await seedIconChannelSection(page);
await installMockBridge(page);
await openChannel(page);
const sectionIconBox = await page
.getByTestId("section-icon-alignment-section")
.boundingBox();
const sectionTitleBox = await page
.getByTestId("section-title-alignment-section")
.boundingBox();
const channelButton = page.getByTestId("channel-general");
const channelIconBox = await channelButton
.locator("svg")
.first()
.boundingBox();
const channelTitleBox = await channelButton
.locator("[data-sidebar-row-label]")
.boundingBox();
expect(sectionIconBox).not.toBeNull();
expect(sectionTitleBox).not.toBeNull();
expect(channelIconBox).not.toBeNull();
expect(channelTitleBox).not.toBeNull();
if (
!sectionIconBox ||
!sectionTitleBox ||
!channelIconBox ||
!channelTitleBox
) {
throw new Error("Custom section alignment geometry is missing");
}
expect(Math.abs(sectionIconBox.x - channelIconBox.x)).toBeLessThanOrEqual(
0.5,
);
expect(Math.abs(sectionTitleBox.x - channelTitleBox.x)).toBeLessThanOrEqual(
0.5,
);
});
async function openAppearance(page: Page, mode: "system" | "light" | "dark") {
// Settings renders at the AppShell level; open it via the profile card
// button, then select the Appearance section.
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByTestId("open-settings").click();
await page.getByTestId("profile-popover-settings").click();
await page.getByTestId("settings-nav-appearance").click();
const panel = page.getByTestId("settings-theme");
await expect(panel).toBeVisible({ timeout: 10_000 });
await page.getByTestId(`appearance-mode-${mode}`).click();
await waitForAnimations(page);
return panel;
}
test("appearance groups theme and preferences into labeled rows", async ({
page,
}) => {
await seedTheme(page, "github-light");
await page.addInitScript((prominentActiveTabStorageKey) => {
window.localStorage.setItem(prominentActiveTabStorageKey, "true");
Object.defineProperty(navigator, "platform", {
configurable: true,
get: () => "MacIntel",
});
}, PROMINENT_ACTIVE_TAB_STORAGE_KEY);
await installMockBridge(page);
await openAppearance(page, "light");
const themeCard = page.getByTestId("appearance-theme-card");
const preferencesCard = page.getByTestId("appearance-preferences-card");
await expect(
themeCard.getByRole("heading", { name: "Theme", exact: true }),
).toBeVisible();
await expect(
preferencesCard.getByRole("heading", {
name: "Preferences",
exact: true,
}),
).toBeVisible();
await expect(
themeCard.getByRole("group", { name: "Color mode" }),
).toBeVisible();
await expect(themeCard.getByTestId("glass-background-toggle")).toBeVisible();
await expect(
themeCard.getByTestId("prominent-active-tab-toggle"),
).toHaveCount(0);
await expect(
preferencesCard.getByTestId("prominent-active-tab-toggle"),
).toHaveCount(0);
await expect(
preferencesCard.getByTestId("thread-layout-trigger"),
).toBeVisible();
const themeStyleTrigger = themeCard.getByTestId("theme-style-trigger");
const themeStyleOptions = themeCard.getByTestId("theme-style-options");
const selectedThemePreview = themeCard.getByTestId(
"theme-style-selected-preview",
);
await expect(themeStyleTrigger).toHaveAttribute(
"aria-label",
"Theme style, Github Light",
);
await expect(themeStyleTrigger).not.toContainText("Github Light");
await expect(themeStyleTrigger).toHaveAttribute("aria-expanded", "false");
await expect(themeStyleTrigger).toHaveCSS(
"background-color",
"rgba(0, 0, 0, 0)",
);
await expect(themeStyleTrigger).toHaveCSS("border-width", "0px");
await expect(selectedThemePreview).toBeVisible();
const selectedThemePreviewSvg = selectedThemePreview.locator("svg");
await expect(selectedThemePreviewSvg).toBeVisible();
const selectedThemePreviewBox = await selectedThemePreview.boundingBox();
const selectedThemePreviewSvgBox =
await selectedThemePreviewSvg.boundingBox();
expect(selectedThemePreviewBox?.width).toBe(168);
expect(selectedThemePreviewBox?.height).toBe(112);
expect(selectedThemePreviewSvgBox?.width).toBeGreaterThan(100);
expect(selectedThemePreviewSvgBox?.height).toBeGreaterThan(70);
await expect(themeStyleOptions).toHaveCount(0);
await themeStyleTrigger.click();
await expect(themeStyleTrigger).toHaveAttribute("aria-expanded", "true");
await expect(themeStyleOptions).toBeVisible();
await themeCard.getByTestId("theme-option-buzz").click();
await expect(themeStyleTrigger).toHaveAttribute("aria-expanded", "true");
await expect(themeStyleOptions).toBeVisible();
await expect(
themeCard.getByTestId("prominent-active-tab-toggle"),
).toBeChecked();
const glassBeforeProminent = await themeCard.evaluate((card) => {
const glass = card.querySelector('[data-testid="glass-background-toggle"]');
const prominent = card.querySelector(
'[data-testid="prominent-active-tab-row"]',
);
return Boolean(
glass &&
prominent &&
glass.compareDocumentPosition(prominent) &
Node.DOCUMENT_POSITION_FOLLOWING,
);
});
expect(glassBeforeProminent).toBe(true);
const colorModeLabelBox = await themeCard
.getByTestId("appearance-color-mode-row")
.locator("p")
.first()
.boundingBox();
const colorModeControlBox = await themeCard
.getByRole("group", { name: "Color mode" })
.boundingBox();
const glassLabelBox = await themeCard
.getByText("Glass background", { exact: true })
.boundingBox();
const glassToggleBox = await themeCard
.getByTestId("glass-background-toggle")
.boundingBox();
const colorModeIndicator = themeCard.getByTestId(
"appearance-color-mode-indicator",
);
const lightModeButton = themeCard.getByTestId("appearance-mode-light");
const initialIndicatorBox = await colorModeIndicator.boundingBox();
const lightModeButtonBox = await lightModeButton.boundingBox();
const themeCardBox = await themeCard.boundingBox();
const preferencesCardBox = await preferencesCard.boundingBox();
expect(colorModeLabelBox).not.toBeNull();
expect(colorModeControlBox).not.toBeNull();
expect(glassLabelBox).not.toBeNull();
expect(glassToggleBox).not.toBeNull();
expect(initialIndicatorBox).not.toBeNull();
expect(lightModeButtonBox).not.toBeNull();
expect(themeCardBox).not.toBeNull();
expect(preferencesCardBox).not.toBeNull();
if (
!colorModeLabelBox ||
!colorModeControlBox ||
!glassLabelBox ||
!glassToggleBox ||
!initialIndicatorBox ||
!lightModeButtonBox ||
!themeCardBox ||
!preferencesCardBox
) {
throw new Error("Appearance card geometry is missing");
}
expect(colorModeLabelBox.x).toBeLessThan(colorModeControlBox.x);
expect(glassLabelBox.x).toBeLessThan(glassToggleBox.x);
expect(themeCardBox.y).toBeLessThan(preferencesCardBox.y);
expect(
Math.abs(
initialIndicatorBox.x +
initialIndicatorBox.width / 2 -
(lightModeButtonBox.x + lightModeButtonBox.width / 2),
),
).toBeLessThanOrEqual(0.5);
await expect(colorModeIndicator).toHaveCSS("transition-duration", "0.25s");
await themeCard.getByTestId("appearance-mode-dark").click();
await waitForAnimations(page);
const movedIndicatorBox = await colorModeIndicator.boundingBox();
const darkModeButtonBox = await themeCard
.getByTestId("appearance-mode-dark")
.boundingBox();
expect(movedIndicatorBox).not.toBeNull();
expect(darkModeButtonBox).not.toBeNull();
if (!movedIndicatorBox || !darkModeButtonBox) {
throw new Error("Color mode indicator geometry is missing");
}
expect(
Math.abs(
movedIndicatorBox.x +
movedIndicatorBox.width / 2 -
(darkModeButtonBox.x + darkModeButtonBox.width / 2),
),
).toBeLessThanOrEqual(0.5);
});
test("appearance picker — system tab (Buzz follows OS)", async ({ page }) => {
await seedTheme(page, "buzz");
await installMockBridge(page);
const panel = await openAppearance(page, "system");
await panel.screenshot({ path: `${SHOTS}/03-picker-system.png` });
});
test("appearance picker — light tab (Buzz)", async ({ page }) => {
await seedTheme(page, "buzz");
await installMockBridge(page);
const panel = await openAppearance(page, "light");
await panel.screenshot({ path: `${SHOTS}/04-picker-light.png` });
});
test("appearance picker — dark tab (Buzz Dark)", async ({ page }) => {
await seedTheme(page, "buzz-dark");
await installMockBridge(page);
const panel = await openAppearance(page, "dark");
await panel.screenshot({ path: `${SHOTS}/05-picker-dark.png` });
});
test("settings nav uses Buzz active pill + hover (light)", async ({ page }) => {
await seedTheme(page, "buzz");
await installMockBridge(page);
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByTestId("open-settings").click();
await page.getByTestId("profile-popover-settings").click();
const sidebar = page.getByTestId("settings-sidebar");
await expect(sidebar).toBeVisible({ timeout: 10_000 });
const profileRow = page.getByTestId("settings-nav-profile");
const profileLabel = profileRow.locator('[data-sidebar="menu-label"]');
await expect(profileRow).toHaveAttribute("data-active", "true");
await expect(profileRow).toHaveCSS("font-weight", "600");
const selectedLabelBox = await profileLabel.boundingBox();
// Appearance is the active section here; its nav row should carry the Buzz
// white active pill (data-active=true), matching the Left Nav treatment.
await page.getByTestId("settings-nav-appearance").click();
await expect(profileRow).toHaveCSS("font-weight", "400");
const unselectedLabelBox = await profileLabel.boundingBox();
expect(selectedLabelBox).not.toBeNull();
expect(unselectedLabelBox).not.toBeNull();
if (!selectedLabelBox || !unselectedLabelBox) {
throw new Error("Settings nav label geometry is missing");
}
expect(Math.abs(selectedLabelBox.width - unselectedLabelBox.width)).toBe(0);
await expectBuzzSettingsPalette(page, "light");
const activeRow = page.getByTestId("settings-nav-appearance");
await expect(activeRow).toHaveAttribute("data-active", "true");
await waitForAnimations(page);
await sidebar.screenshot({ path: `${SHOTS}/06-settings-nav-light.png` });
});
test("settings nav uses Buzz active pill + hover (dark)", async ({ page }) => {
await seedTheme(page, "buzz-dark");
await installMockBridge(page);
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByTestId("open-settings").click();
await page.getByTestId("profile-popover-settings").click();
const sidebar = page.getByTestId("settings-sidebar");
await expect(sidebar).toBeVisible({ timeout: 10_000 });
await page.getByTestId("settings-nav-appearance").click();
await expectBuzzSettingsPalette(page, "dark");
await expect(page.getByTestId("settings-content-surface")).toHaveCSS(
"background-color",
"rgb(26, 26, 26)",
);
await waitForAnimations(page);
await sidebar.screenshot({ path: `${SHOTS}/07-settings-nav-dark.png` });
await page.getByTestId("settings-view").screenshot({
path: `${SHOTS}/09-settings-content-dark.png`,
});
});
test("prominent active tab is opt-in and switches selection surfaces", async ({
page,
}) => {
await seedTheme(page, "buzz");
await installMockBridge(page);
await openAppearance(page, "light");
const root = page.locator("html");
const activeRow = page.getByTestId("settings-nav-appearance");
const toggle = page.getByTestId("prominent-active-tab-toggle");
await expect(toggle).not.toBeChecked();
await expect(root).not.toHaveAttribute("data-prominent-active-tab", "");
await expect(activeRow).toHaveCSS("background-color", "rgba(0, 0, 0, 0.07)");
const subtleTextStyle = await activeRow.evaluate((element) => {
const styles = getComputedStyle(element);
return { color: styles.color, fontWeight: styles.fontWeight };
});
await expect
.poll(() =>
page.evaluate(
(key) => window.localStorage.getItem(key),
PROMINENT_ACTIVE_TAB_STORAGE_KEY,
),
)
.toBeNull();
await toggle.click();
await expect(toggle).toBeChecked();
await expect(root).toHaveAttribute("data-prominent-active-tab", "");
await expect(activeRow).toHaveCSS(
"background-color",
"rgba(255, 255, 255, 0.82)",
);
await expect
.poll(() =>
page.evaluate(
(key) => window.localStorage.getItem(key),
PROMINENT_ACTIVE_TAB_STORAGE_KEY,
),
)
.toBe("true");
const prominentTextStyle = await activeRow.evaluate((element) => {
const styles = getComputedStyle(element);
return { color: styles.color, fontWeight: styles.fontWeight };
});
expect(prominentTextStyle).toEqual(subtleTextStyle);
await toggle.click();
await expect(root).not.toHaveAttribute("data-prominent-active-tab", "");
await expect(activeRow).toHaveCSS("background-color", "rgba(0, 0, 0, 0.07)");
await expect
.poll(() =>
page.evaluate(
(key) => window.localStorage.getItem(key),
PROMINENT_ACTIVE_TAB_STORAGE_KEY,
),
)
.toBe("false");
});
test("prominent channel and direct-message rows share one flat active state", async ({
page,
}) => {
await seedTheme(page, "buzz");
await page.addInitScript(
({ key }) => window.localStorage.setItem(key, "true"),
{ key: PROMINENT_ACTIVE_TAB_STORAGE_KEY },
);
await installMockBridge(page);
await openChannel(page);
const channelRow = page.getByTestId("channel-general");
const directMessageRow = page.getByTestId("channel-alice-tyler");
await expect(channelRow).toHaveCSS(
"background-color",
"rgba(255, 255, 255, 0.82)",
);
await expect(channelRow).toHaveCSS("box-shadow", "none");
await channelRow.hover();
await expect(channelRow).toHaveCSS(
"background-color",
"rgba(255, 255, 255, 0.82)",
);
await directMessageRow.click();
await expect(page.getByTestId("chat-title")).toHaveText("alice-tyler");
await expect(directMessageRow).toHaveCSS(
"background-color",
"rgba(255, 255, 255, 0.82)",
);
await expect(directMessageRow).toHaveCSS("box-shadow", "none");
await directMessageRow.hover();
await expect(directMessageRow).toHaveCSS(
"background-color",
"rgba(255, 255, 255, 0.82)",
);
});
for (const { activeSurface, hoverSurface, mode, theme } of [
{
activeSurface: "rgba(0, 0, 0, 0.07)",
hoverSurface: "rgba(0, 0, 0, 0.04)",
mode: "light" as const,
theme: "buzz",
},
{
activeSurface: "rgba(255, 255, 255, 0.16)",
hoverSurface: "rgba(255, 255, 255, 0.04)",
mode: "dark" as const,
theme: "buzz-dark",
},
]) {
test(`non-prominent ${theme} selection matches production`, async ({
page,
}) => {
await seedTheme(page, theme);
await page.addInitScript(
({ key }) => window.localStorage.setItem(key, "false"),
{ key: PROMINENT_ACTIVE_TAB_STORAGE_KEY },
);
await installMockBridge(page);
await openChannel(page);
const root = page.locator("html");
const sidebar = page.getByTestId("app-sidebar");
const activeRow = page.getByTestId("channel-general");
await expect(root).toHaveClass(
new RegExp(`(^|\\s)${mode === "dark" ? "dark" : "light"}($|\\s)`),
);
await expect(root).not.toHaveAttribute("data-prominent-active-tab", "");
await expect(activeRow).toHaveCSS("background-color", activeSurface);
await expect(activeRow).toHaveCSS("box-shadow", "none");
await expect(activeRow).toHaveCSS("font-weight", "400");
await activeRow.hover();
await expect(activeRow).toHaveCSS("background-color", activeSurface);
const inactiveRow = page.getByTestId("channel-random");
await inactiveRow.hover();
await expect(inactiveRow).toHaveCSS("background-color", hoverSurface);
const expectedForeground = await sidebar.evaluate((element) => {
const probe = document.createElement("span");
probe.style.color = "hsl(var(--sidebar-active-foreground))";
element.append(probe);
const color = getComputedStyle(probe).color;
probe.remove();
return color;
});
await expect(activeRow).toHaveCSS("color", expectedForeground);
});
}
for (const { mode, theme } of [
{ mode: "light" as const, theme: "github-light" },
{ mode: "dark" as const, theme: "github-dark" },
]) {
test(`${theme} ignores the Buzz prominent preference`, async ({ page }) => {
await seedTheme(page, theme);
await page.addInitScript(
({ key }) => window.localStorage.setItem(key, "true"),
{ key: PROMINENT_ACTIVE_TAB_STORAGE_KEY },
);
await installMockBridge(page);
await openAppearance(page, mode);
const root = page.locator("html");
const activeRow = page.getByTestId("settings-nav-appearance");
await expect(page.getByTestId("prominent-active-tab-row")).toHaveCount(0);
await expect(root).not.toHaveAttribute("data-prominent-active-tab", "");
const productionStyle = await page.evaluate(() => {
const sidebar = document.querySelector<HTMLElement>(
'[data-testid="settings-sidebar"]',
);
const row = document.querySelector<HTMLElement>(
'[data-testid="settings-nav-appearance"]',
);
if (!sidebar || !row) return null;
const probe = document.createElement("span");
probe.style.backgroundColor = "hsl(var(--sidebar-active))";
probe.style.color = "hsl(var(--sidebar-active-foreground))";
sidebar.append(probe);
const probeStyles = getComputedStyle(probe);
const rowStyles = getComputedStyle(row);
const result = {
expectedBackground: probeStyles.backgroundColor,
expectedForeground: probeStyles.color,
background: rowStyles.backgroundColor,
color: rowStyles.color,
fontWeight: rowStyles.fontWeight,
};
probe.remove();
return result;
});
expect(productionStyle).not.toBeNull();
expect(productionStyle?.background).toBe(
productionStyle?.expectedBackground,
);
expect(productionStyle?.color).toBe(productionStyle?.expectedForeground);
await expect(activeRow).toHaveCSS(
"font-weight",
productionStyle?.fontWeight ?? "",
);
});
}
test("settings content uses the same inset surface as the main app", async ({
page,
}) => {
await seedTheme(page, "buzz");
await installMockBridge(page);
await page.goto("/", { waitUntil: "domcontentloaded" });
const searchBox = await page.getByTestId("open-search").boundingBox();
await page.getByTestId("open-settings").click();
await page.getByTestId("profile-popover-settings").click();
const settingsView = page.getByTestId("settings-view");
const contentSurface = page.getByTestId("settings-content-surface");
const settingsTopChrome = page.getByTestId("settings-top-chrome");
const settingsSidebarTopChrome = page.getByTestId(
"settings-sidebar-top-chrome",
);
const backToAppBox = await page
.getByTestId("settings-back-to-app")
.boundingBox();
await expect(contentSurface).toBeVisible({ timeout: 10_000 });
for (const dragRegion of [settingsTopChrome, settingsSidebarTopChrome]) {
await expect(dragRegion).toHaveAttribute(
"data-tauri-drag-region",
/^(?:|true)$/,
);
await expect(dragRegion).toHaveCSS("cursor", "default");
await expect(dragRegion).toHaveCSS("user-select", "none");
}
await expect(page.getByTestId("settings-content-scroll")).toHaveCSS(
"padding-top",
"24px",
);
const viewBox = await settingsView.boundingBox();
const surfaceBox = await contentSurface.boundingBox();
expect(searchBox).not.toBeNull();
expect(backToAppBox).not.toBeNull();
expect(viewBox).not.toBeNull();
expect(surfaceBox).not.toBeNull();
if (!searchBox || !backToAppBox || !viewBox || !surfaceBox) {
throw new Error("Settings layout is missing");
}
expect(Math.abs(backToAppBox.y - searchBox.y)).toBeLessThanOrEqual(0.5);
// Match the normal app shell: a fixed 40px top chrome strip, then a 1px
// top/left inset and 8px right/bottom inset around the rounded content card.
expect(surfaceBox.y - viewBox.y).toBe(41);
expect(surfaceBox.x - viewBox.x).toBe(1);
expect(viewBox.x + viewBox.width - (surfaceBox.x + surfaceBox.width)).toBe(8);
expect(viewBox.y + viewBox.height - (surfaceBox.y + surfaceBox.height)).toBe(
8,
);
const topChromeBox = await settingsTopChrome.boundingBox();
const settingsHeadingBox = await page
.getByRole("heading", { level: 1, name: "Profile" })
.boundingBox();
expect(topChromeBox).not.toBeNull();
expect(settingsHeadingBox).not.toBeNull();
if (!topChromeBox || !settingsHeadingBox) {
throw new Error("Settings drag region or title is missing");
}
await page.mouse.move(
topChromeBox.x + topChromeBox.width / 2,
topChromeBox.y + topChromeBox.height / 2,
);
await page.mouse.down();
await page.mouse.move(
settingsHeadingBox.x + settingsHeadingBox.width / 2,
settingsHeadingBox.y + settingsHeadingBox.height / 2,
{ steps: 5 },
);
await page.mouse.up();
expect(await page.evaluate(() => window.getSelection()?.toString())).toBe("");
await waitForAnimations(page);
await settingsView.screenshot({
path: `${SHOTS}/08-settings-content-inset.png`,
});
});
test("appearance hides accent picker under Buzz", async ({ page }) => {
await seedTheme(page, "buzz");
await installMockBridge(page);
const panel = await openAppearance(page, "light");
// The accent picker is hidden while a Buzz theme is active. Its neutral
// swatch testid must not be present.
await expect(page.getByTestId("accent-color-neutral")).toHaveCount(0);
await panel.screenshot({ path: `${SHOTS}/10-appearance-no-accent.png` });
});
test("glass background keeps the content panel solid", async ({ page }) => {
await seedTheme(page, "buzz");
await page.addInitScript(() => {
(window as typeof window & { isTauri?: boolean }).isTauri = true;
Object.defineProperty(navigator, "platform", {
configurable: true,
get: () => "MacIntel",
});
});
await installMockBridge(page);
const panel = await openAppearance(page, "light");
const toggle = page.getByTestId("glass-background-toggle");
const opacitySlider = page.getByTestId("glass-opacity-slider");
const root = page.locator("html");
await expect(toggle).toBeEnabled();
await expect(toggle).not.toBeChecked();
await expect
.poll(() =>
page.evaluate(
(key) => window.localStorage.getItem(key),
GLASS_BACKGROUND_STORAGE_KEY,
),
)
.toBeNull();
await expect(opacitySlider).toHaveCount(0);
await expect(root).not.toHaveAttribute("data-glass-background", "");
await expect(page.locator(".buzz-theme-gradient-underlay")).not.toHaveCSS(
"background-image",
"none",
);
await toggle.click();
await expect(toggle).toBeChecked();
await expect(opacitySlider).toBeVisible();
await expect(opacitySlider).toHaveClass(/buzz-avatar-framing-slider/);
await expect(opacitySlider).toHaveAttribute("aria-valuenow", "65");
await expect(opacitySlider).toHaveCSS("height", "32px");
const matchingRadiusControls = [
page.getByTestId("appearance-color-mode-control"),
page.getByTestId("appearance-color-mode-indicator"),
page.getByTestId("theme-style-trigger"),
page.getByTestId("link-preview-style-trigger"),
page.getByTestId("thread-layout-trigger"),
];
for (const control of matchingRadiusControls) {
await expect(control).toHaveCSS("border-radius", "8px");
}
await page.getByTestId("link-preview-style-trigger").click();
await expect(page.getByTestId("link-preview-style-menu")).toHaveCSS(
"border-radius",
"8px",
);
await page.keyboard.press("Escape");
await page.getByTestId("thread-layout-trigger").click();
await expect(page.getByTestId("thread-layout-menu")).toHaveCSS(
"border-radius",
"8px",
);
await page.keyboard.press("Escape");
await expect(page.getByTestId("glass-opacity-value")).toHaveCount(0);
await expect(
opacitySlider.locator(".buzz-avatar-framing-slider-handle"),
).toHaveCSS("opacity", "1");
await expect(root).toHaveAttribute("data-glass-background", "");
const buzzSettingOrder = await page
.getByTestId("appearance-theme-card")
.locator(
'[data-testid="appearance-color-mode-row"], [data-testid="theme-style-row"], [data-testid="glass-background-row"], [data-testid="glass-opacity-row"], [data-testid="prominent-active-tab-row"]',
)
.evaluateAll((rows) => rows.map((row) => row.getAttribute("data-testid")));
expect(buzzSettingOrder).toEqual([
"appearance-color-mode-row",
"theme-style-row",
"glass-background-row",
"glass-opacity-row",
"prominent-active-tab-row",
]);
await expect(page.locator(".buzz-theme-gradient-underlay")).toHaveCSS(
"background-image",
"none",
);
for (const canvas of [root, page.locator("body"), page.locator("#root")]) {
await expect(canvas).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
}
await expect(
page.getByTestId("settings-sidebar").locator('[data-sidebar="sidebar"]'),
).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
await expect(page.getByTestId("settings-content-surface")).not.toHaveCSS(
"background-color",
"rgba(0, 0, 0, 0)",
);
await expect
.poll(() =>
page.evaluate(() =>
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).some(
(entry) =>
entry.command === "set_window_vibrancy" &&
(entry.payload as { enabled?: boolean } | undefined)?.enabled ===
true,
),
),
)
.toBe(true);
await opacitySlider.press("Home");
await expect(opacitySlider).toHaveAttribute("aria-valuenow", "30");
await expect
.poll(() =>
page.evaluate(() =>
document.documentElement.style.getPropertyValue(
"--glass-background-opacity",
),
),
)
.toBe("30%");
await expect
.poll(() =>
page.evaluate(
(storageKey) => localStorage.getItem(storageKey),
GLASS_OPACITY_STORAGE_KEY,
),
)
.toBe("30");
await waitForAnimations(page);
await panel.screenshot({ path: `${SHOTS}/11-appearance-glass.png` });
await toggle.click();
await expect(root).not.toHaveAttribute("data-glass-background", "");
await expect(opacitySlider).toHaveCount(0);
await expect
.poll(() =>
page.evaluate(
(storageKey) => localStorage.getItem(storageKey),
GLASS_BACKGROUND_STORAGE_KEY,
),
)
.toBe("false");
});
test("glass background is unavailable on Linux", async ({ page }) => {
await seedTheme(page, "buzz");
await page.addInitScript((storageKey) => {
window.localStorage.setItem(storageKey, "true");
(window as typeof window & { isTauri?: boolean }).isTauri = true;
Object.defineProperty(navigator, "platform", {
configurable: true,
get: () => "Linux x86_64",
});
Object.defineProperty(navigator, "userAgent", {
configurable: true,
get: () => "Buzz Desktop Linux",
});
}, GLASS_BACKGROUND_STORAGE_KEY);
await installMockBridge(page);
await openAppearance(page, "light");
await expect(page.getByTestId("glass-background-row")).toHaveCount(0);
await expect(page.getByTestId("glass-background-toggle")).toHaveCount(0);
await expect(page.getByTestId("glass-opacity-slider")).toHaveCount(0);
await expect(page.locator("html")).not.toHaveAttribute(
"data-glass-background",
"",
);
await expect
.poll(() =>
page.evaluate(() =>
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).some(
(entry) => entry.command === "set_window_vibrancy",
),
),
)
.toBe(false);
await expect
.poll(() =>
page.evaluate(
(storageKey) => window.localStorage.getItem(storageKey),
GLASS_BACKGROUND_STORAGE_KEY,
),
)
.toBe("true");
});
test("non-Buzz glass preserves the selected theme sidebar tint", async ({
page,
}) => {
await seedTheme(page, "rose-pine-dawn");
await page.addInitScript(() => {
(window as typeof window & { isTauri?: boolean }).isTauri = true;
Object.defineProperty(navigator, "platform", {
configurable: true,
get: () => "MacIntel",
});
});
await installMockBridge(page);
await openAppearance(page, "light");
const root = page.locator("html");
await expect(root).not.toHaveAttribute("data-buzz-sidebar", "");
await page.getByTestId("glass-background-toggle").click();
await expect(root).toHaveAttribute("data-glass-background", "");
const tint = await page
.locator(".buzz-theme-gradient-layer")
.evaluate((element) => {
const rootStyles = getComputedStyle(document.documentElement);
const sidebar = rootStyles
.getPropertyValue("--sidebar-background")
.trim();
const staticFallback = rootStyles.getPropertyValue("--sidebar").trim();
const probe = document.createElement("div");
probe.style.backgroundColor = `hsl(${sidebar} / 65%)`;
document.body.appendChild(probe);
const expected = getComputedStyle(probe).backgroundColor;
probe.remove();
return {
actual: getComputedStyle(element).backgroundColor,
expected,
sidebar,
staticFallback,
};
});
expect(tint.sidebar).not.toBe(tint.staticFallback);
expect(tint.actual).toBe(tint.expected);
});
test("accent picker reveals/hides when toggling Buzz", async ({ page }) => {
// Start on a non-Buzz theme so the accent picker is present, then select the
// Buzz tile — the picker should animate out and unmount. Reselecting a
// non-Buzz tile brings it back. Asserts the presence toggle (the motion
// wrapper) works end to end.
await seedTheme(page, "github-light");
await page.addInitScript(() => {
Object.defineProperty(navigator, "platform", {
configurable: true,
get: () => "MacIntel",
});
});
await installMockBridge(page);
await openAppearance(page, "light");
await expect(page.getByTestId("accent-color-neutral")).toBeVisible();
const nonBuzzSettingOrder = await page
.getByTestId("appearance-theme-card")
.locator(
'[data-testid="appearance-color-mode-row"], [data-testid="theme-style-row"], [data-testid="accent-color-options"], [data-testid="glass-background-row"], [data-testid="prominent-active-tab-row"]',
)
.evaluateAll((rows) => rows.map((row) => row.getAttribute("data-testid")));
expect(nonBuzzSettingOrder).toEqual([
"appearance-color-mode-row",
"theme-style-row",
"accent-color-options",
"glass-background-row",
]);
// Switch to Buzz — picker should leave (allow the exit animation to settle).
await page.getByTestId("theme-style-trigger").click();
await page.getByTestId("theme-option-buzz").click();
await expect(page.getByTestId("theme-style-trigger")).toHaveAttribute(
"aria-expanded",
"true",
);
await expect(page.getByTestId("accent-color-neutral")).toHaveCount(0);
// Back to a non-Buzz theme — picker returns.
await page.getByTestId("theme-option-github-light").click();
await expect(page.getByTestId("accent-color-neutral")).toBeVisible();
await expect(page.getByTestId("theme-style-trigger")).toHaveAttribute(
"aria-expanded",
"true",
);
const swatchBoxes = await page
.getByTestId("accent-color-options")
.locator("button")
.evaluateAll((buttons) =>
buttons.map((button) => {
const box = button.getBoundingClientRect();
return { x: box.x, y: box.y };
}),
);
expect(swatchBoxes).toHaveLength(10);
expect(new Set(swatchBoxes.map((box) => Math.round(box.y))).size).toBe(1);
await expect(page.getByTestId("accent-color-options")).toHaveCSS(
"overflow-x",
"auto",
);
await expect(page.getByTestId("accent-color-blue")).toHaveAttribute(
"aria-pressed",
"true",
);
await expect(
page.getByTestId("accent-color-blue").getByTestId("accent-color-selection"),
).toBeVisible();
await waitForAnimations(page);
await page.getByTestId("settings-theme").screenshot({
path: `${SHOTS}/12-appearance-theme-and-accents.png`,
});
const accentOptions = page.getByTestId("accent-color-options");
await accentOptions.scrollIntoViewIfNeeded();
await waitForAnimations(page);
await accentOptions.screenshot({
path: `${SHOTS}/13-appearance-accent-row.png`,
});
});
test("Buzz light and dark modes apply live without a reload", async ({
page,
}) => {
await seedTheme(page, "buzz");
await installMockBridge(page);
await openAppearance(page, "light");
await expectAppliedBuzzTheme(page, "buzz");
const lightGradient = await expectBuzzGradientPaint(page, "light");
await page.getByTestId("appearance-mode-dark").click();
await expectAppliedBuzzTheme(page, "buzz-dark");
const darkGradient = await expectBuzzGradientPaint(page, "dark");
expect(darkGradient).not.toBe(lightGradient);
await page.getByTestId("appearance-mode-light").click();
await expectAppliedBuzzTheme(page, "buzz");
await expectBuzzGradientPaint(page, "light");
// Exercise the overlap that previously let a slower, stale theme load win.
await page.getByTestId("appearance-mode-dark").click();
await page.getByTestId("appearance-mode-light").click();
await expectAppliedBuzzTheme(page, "buzz");
});
test("Buzz follows native system theme changes without a reload", async ({
page,
}) => {
await seedTheme(page, "buzz");
await page.addInitScript(() => {
(window as typeof window & { isTauri?: boolean }).isTauri = true;
});
await installMockBridge(page);
await openAppearance(page, "system");
await emitNativeThemeChange(page, "dark");
await expectAppliedBuzzTheme(page, "buzz-dark", "buzz");
await expectBuzzGradientPaint(page, "dark");
await emitNativeThemeChange(page, "light");
await expectAppliedBuzzTheme(page, "buzz", "buzz");
await expectBuzzGradientPaint(page, "light");
});