mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
**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 | | --- | --- | |  |  | ## 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>
161 lines
5.5 KiB
TypeScript
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",
|
|
);
|
|
});
|
|
}
|