diff --git a/src/ui/Tooltip.module.css b/src/ui/Tooltip.module.css index 48cd3c4a..d5717f3d 100644 --- a/src/ui/Tooltip.module.css +++ b/src/ui/Tooltip.module.css @@ -5,7 +5,7 @@ display: flex; align-items: center; gap: 8px; - max-width: 360px; + max-width: min(720px, calc(100vw - 32px)); padding: 6px 10px; border-radius: var(--radius-tooltip, 6px); background: var(--bg-tooltip); @@ -15,17 +15,23 @@ font-size: var(--fs-ui); line-height: 16px; pointer-events: none; - white-space: nowrap; opacity: 0; } .tooltip[data-placed] { opacity: 1; transition: opacity var(--dur-fast) var(--ease); } +/* Long paths wrap inside the bubble; the shortcut stays on one line. */ +.text { + min-width: 0; + overflow-wrap: anywhere; +} .shortcut { font-family: var(--font-keys); font-size: var(--fs-ui); color: color-mix(in srgb, var(--fg-tooltip) 62%, transparent); + white-space: nowrap; + flex-shrink: 0; } @media (prefers-reduced-motion: reduce) { diff --git a/src/ui/Tooltip.test.tsx b/src/ui/Tooltip.test.tsx new file mode 100644 index 00000000..813bd8a8 --- /dev/null +++ b/src/ui/Tooltip.test.tsx @@ -0,0 +1,67 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { act, cleanup, fireEvent, render, screen } from "@testing-library/react"; +import { TOOLTIP_DELAY, TooltipHost, tooltipAttrs } from "./Tooltip"; +import styles from "./Tooltip.module.css"; + +describe("tooltipAttrs", () => { + it("returns empty object when text is missing", () => { + expect(tooltipAttrs(undefined)).toEqual({}); + expect(tooltipAttrs("")).toEqual({}); + }); + + it("sets data-tooltip and optional shortcut", () => { + expect(tooltipAttrs("Close Tab")).toEqual({ "data-tooltip": "Close Tab" }); + expect(tooltipAttrs("Save All", "Ctrl+S")).toEqual({ + "data-tooltip": "Save All", + "data-tooltip-shortcut": "Ctrl+S", + }); + }); +}); + +describe("TooltipHost", () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + cleanup(); + vi.useRealTimers(); + }); + + it("shows tooltip text with styles.text class and shortcut after delay", async () => { + render( +