Files
buzz/desktop/tests/e2e/tooltip-semantics.spec.ts
Taylor Ho 03bfc837cb fix(desktop-tooltip): increase surface contrast (#6897)
**Category:** fix
**User Impact:** Tooltips now use a higher-contrast surface, making
their labels easier to read across desktop themes.

**Problem:** Tooltip surfaces could blend into hovered content, making
labels difficult to distinguish.

**Solution:** Use the existing popover surface and foreground tokens for
clearer visual separation and theme-aware contrast.

<details>
<summary>File changes</summary>

**desktop/src/shared/ui/tooltip.tsx**
Updates shared tooltip colors to use higher-contrast semantic surface
and foreground tokens.

</details>

## Reproduction steps

1. Run the desktop app and hover a control that displays a tooltip.
2. Compare the tooltip against the content beneath it in both light and
dark themes.
3. Confirm the tooltip surface remains clearly distinguishable and its
label is readable.

## Screenshots

| Before | After |
| --- | --- |
| ![Tooltip using the lower-contrast secondary
surface](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6897/tooltip-before.png)
| ![Tooltip using the higher-contrast popover
surface](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6897/tooltip-after.png)
|

## Validation

- `pnpm build:e2e`
- `pnpm exec playwright test tests/e2e/tooltip-semantics.spec.ts
--project=smoke` — 4 passed
- `pnpm exec biome check src/shared/ui/tooltip.tsx
tests/e2e/tooltip-semantics.spec.ts`
- Pre-push desktop checks, typecheck, and unit tests
- `git diff --check`
- Full smoke project attempted; 345 of 1,222 tests passed without
failure before the 10-minute local execution limit.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
2026-08-26 20:56:21 +00:00

161 lines
5.5 KiB
TypeScript

import { expect, test, type Locator, type Page } from "@playwright/test";
import { installMockBridge } from "../helpers/bridge";
import { waitForAnimations } from "../helpers/animations";
const THEMES = ["buzz", "catppuccin-mocha"] as const;
async function seedTheme(page: Page, theme: (typeof THEMES)[number]) {
await page.addInitScript((value) => {
window.localStorage.setItem("buzz-theme", value);
}, theme);
}
async function expectPopoverSurface(tooltip: Locator) {
const colors = await tooltip.evaluate((element) => {
const sample = document.createElement("div");
sample.style.cssText =
"position:fixed;visibility:hidden;background:hsl(var(--popover));color:hsl(var(--popover-foreground))";
document.body.append(sample);
const tooltipStyle = getComputedStyle(element);
const sampleStyle = getComputedStyle(sample);
const result = {
background: tooltipStyle.backgroundColor,
color: tooltipStyle.color,
expectedBackground: sampleStyle.backgroundColor,
expectedColor: sampleStyle.color,
};
sample.remove();
return result;
});
expect(colors.background).toBe(colors.expectedBackground);
expect(colors.color).toBe(colors.expectedColor);
}
async function expectMutedSupportingText(
trigger: Locator,
expectedText: string | RegExp,
) {
await trigger.hover();
const footer = trigger
.page()
.getByRole("tooltip")
.locator('[data-buzz-tooltip-metadata-type=""]');
await expect(footer).toHaveText(expectedText);
await expect(footer).toHaveClass(/text-secondary-foreground\/80/);
await expect(footer).not.toHaveClass(/text-primary-foreground/);
}
for (const theme of THEMES) {
test(`simple and rich tooltips use the popover surface — ${theme}`, async ({
page,
}) => {
await seedTheme(page, theme);
await installMockBridge(page, {
personas: [
{
id: "tooltip-reviewer",
displayName: "Tooltip Reviewer",
systemPrompt: "Review tooltip contrast.",
},
],
teams: [
{
id: "tooltip-team",
name: "Contrast Crew",
description: "Checks semantic surface contrast",
personaIds: ["tooltip-reviewer"],
},
],
});
await page.goto("/");
await page.getByTestId("channel-random").click();
await page.getByTestId("channel-members-trigger").hover();
const simpleTooltip = page.getByRole("tooltip");
await expect(simpleTooltip).toHaveText("Channel members");
await expectPopoverSurface(simpleTooltip);
await page.mouse.move(0, 0);
await page.getByTestId("channel-intro-action-create-agent").click();
const dialog = page.getByTestId("add-channel-bot-dialog");
await expect(dialog).toBeVisible();
await dialog.getByRole("button", { name: "Contrast Crew" }).hover();
const richTooltip = page.getByRole("tooltip");
await expect(richTooltip).toContainText("Checks semantic surface contrast");
await expect(richTooltip).toContainText("Tooltip Reviewer");
await expectPopoverSurface(richTooltip);
await expect(
richTooltip.getByText("Checks semantic surface contrast"),
).toHaveClass(/text-secondary-foreground\/80/);
await expect(richTooltip.getByText("Tooltip Reviewer")).toHaveClass(
/text-secondary-foreground/,
);
await expect(
richTooltip.getByTestId("team-tooltip-persona-chip"),
).toHaveClass(/bg-secondary-foreground\/10/);
await expect(
richTooltip.getByTestId("team-tooltip-persona-avatar"),
).toHaveClass(/bg-secondary-foreground\/20.*text-secondary-foreground/);
await waitForAnimations(page);
await dialog.screenshot({
path: `test-results/tooltip-semantics/${theme}-team-tooltip.png`,
});
});
test(`link tooltip supporting text uses the muted secondary foreground — ${theme}`, async ({
page,
}) => {
await seedTheme(page, theme);
await installMockBridge(page);
await page.goto("/");
await page.getByTestId("channel-general").click();
const channelId = "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50";
const owner = "a".repeat(64);
const issueId = "b".repeat(64);
const links = [
`buzz://channel/${channelId}`,
`buzz://message?channel=${channelId}&id=mock-general-welcome`,
`buzz://issue?id=${issueId}&owner=${owner}&d=buzz-world`,
].join(" ");
const composer = page.getByTestId("message-input");
await composer.evaluate((element, text) => {
const clipboardData = new DataTransfer();
clipboardData.setData("text/plain", text);
element.dispatchEvent(
new ClipboardEvent("paste", {
bubbles: true,
cancelable: true,
clipboardData,
}),
);
}, links);
const composerChips = composer.locator('[data-composer-buzz-link=""]');
await expect(composerChips).toHaveCount(3);
await page.getByTestId("send-message").click();
const row = page.getByTestId("message-row").last();
await expect(row).toBeVisible();
await expectMutedSupportingText(
row.getByRole("button", { name: "Open channel general" }),
/Public channel · Active (just now|\d+[mhdw] ago)/,
);
await page.mouse.move(0, 0);
await expectMutedSupportingText(
row.getByRole("button", { name: "Open message in channel general" }),
/#general · .+ · (just now|\d+[mhdw] ago)/,
);
await page.mouse.move(0, 0);
await expectMutedSupportingText(
row.getByRole("button", {
name: /Open issue .* in repository buzz-world/,
}),
"Issue · buzz-world",
);
});
}