Skip to content

Commit fd9211a

Browse files
committed
fix: resolve tooltip overflow issue by enabling text wrapping
- Remove overflow-hidden class from TooltipContent that prevented wrapping - Add max-w-[300px] and break-words classes for proper text wrapping - Add optional maxWidth prop to StandardTooltip for customization - Add comprehensive tests for tooltip behavior Addresses feedback from PR #5098 where tooltips were extending beyond the plugin viewport boundaries when content was too wide.
1 parent 11d92e3 commit fd9211a

File tree

3 files changed

+190
-2
lines changed

3 files changed

+190
-2
lines changed
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import { render, screen, waitFor } from "@testing-library/react"
2+
import userEvent from "@testing-library/user-event"
3+
import { describe, it, expect } from "vitest"
4+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../tooltip"
5+
import { StandardTooltip } from "../standard-tooltip"
6+
7+
describe("Tooltip", () => {
8+
it("should render tooltip content on hover", async () => {
9+
const user = userEvent.setup()
10+
11+
render(
12+
<TooltipProvider delayDuration={0}>
13+
<Tooltip>
14+
<TooltipTrigger>Hover me</TooltipTrigger>
15+
<TooltipContent>Tooltip text</TooltipContent>
16+
</Tooltip>
17+
</TooltipProvider>,
18+
)
19+
20+
const trigger = screen.getByText("Hover me")
21+
await user.hover(trigger)
22+
23+
await waitFor(
24+
() => {
25+
const tooltips = screen.getAllByText("Tooltip text")
26+
expect(tooltips.length).toBeGreaterThan(0)
27+
},
28+
{ timeout: 1000 },
29+
)
30+
})
31+
32+
it("should apply text wrapping classes", async () => {
33+
const user = userEvent.setup()
34+
35+
render(
36+
<TooltipProvider delayDuration={0}>
37+
<Tooltip>
38+
<TooltipTrigger>Hover me</TooltipTrigger>
39+
<TooltipContent>
40+
This is a very long tooltip text that should wrap when it reaches the maximum width
41+
</TooltipContent>
42+
</Tooltip>
43+
</TooltipProvider>,
44+
)
45+
46+
const trigger = screen.getByText("Hover me")
47+
await user.hover(trigger)
48+
49+
await waitFor(
50+
() => {
51+
const tooltips = screen.getAllByText(/This is a very long tooltip text/)
52+
const visibleTooltip = tooltips.find((el) => el.getAttribute("role") !== "tooltip")
53+
expect(visibleTooltip).toHaveClass("max-w-[300px]", "break-words")
54+
},
55+
{ timeout: 1000 },
56+
)
57+
})
58+
59+
it("should not have overflow-hidden class", async () => {
60+
const user = userEvent.setup()
61+
62+
render(
63+
<TooltipProvider delayDuration={0}>
64+
<Tooltip>
65+
<TooltipTrigger>Hover me</TooltipTrigger>
66+
<TooltipContent>Tooltip text</TooltipContent>
67+
</Tooltip>
68+
</TooltipProvider>,
69+
)
70+
71+
const trigger = screen.getByText("Hover me")
72+
await user.hover(trigger)
73+
74+
await waitFor(
75+
() => {
76+
const tooltips = screen.getAllByText("Tooltip text")
77+
const visibleTooltip = tooltips.find((el) => el.getAttribute("role") !== "tooltip")
78+
expect(visibleTooltip).not.toHaveClass("overflow-hidden")
79+
},
80+
{ timeout: 1000 },
81+
)
82+
})
83+
})
84+
85+
describe("StandardTooltip", () => {
86+
it("should render with default delay", async () => {
87+
const user = userEvent.setup()
88+
89+
render(
90+
<TooltipProvider delayDuration={300}>
91+
<StandardTooltip content="Tooltip text">
92+
<button>Hover me</button>
93+
</StandardTooltip>
94+
</TooltipProvider>,
95+
)
96+
97+
const trigger = screen.getByText("Hover me")
98+
await user.hover(trigger)
99+
100+
await waitFor(
101+
() => {
102+
const tooltips = screen.getAllByText("Tooltip text")
103+
expect(tooltips.length).toBeGreaterThan(0)
104+
},
105+
{ timeout: 1000 },
106+
)
107+
})
108+
109+
it("should apply custom maxWidth", async () => {
110+
const user = userEvent.setup()
111+
112+
render(
113+
<TooltipProvider delayDuration={0}>
114+
<StandardTooltip content="Long tooltip text" maxWidth={200}>
115+
<button>Hover me</button>
116+
</StandardTooltip>
117+
</TooltipProvider>,
118+
)
119+
120+
const trigger = screen.getByText("Hover me")
121+
await user.hover(trigger)
122+
123+
await waitFor(
124+
() => {
125+
const tooltips = screen.getAllByText("Long tooltip text")
126+
const visibleTooltip = tooltips.find((el) => el.getAttribute("role") !== "tooltip")
127+
expect(visibleTooltip).toHaveStyle({ maxWidth: "200px" })
128+
},
129+
{ timeout: 1000 },
130+
)
131+
})
132+
133+
it("should apply custom maxWidth as string", async () => {
134+
const user = userEvent.setup()
135+
136+
render(
137+
<TooltipProvider delayDuration={0}>
138+
<StandardTooltip content="Long tooltip text" maxWidth="15rem">
139+
<button>Hover me</button>
140+
</StandardTooltip>
141+
</TooltipProvider>,
142+
)
143+
144+
const trigger = screen.getByText("Hover me")
145+
await user.hover(trigger)
146+
147+
await waitFor(
148+
() => {
149+
const tooltips = screen.getAllByText("Long tooltip text")
150+
const visibleTooltip = tooltips.find((el) => el.getAttribute("role") !== "tooltip")
151+
expect(visibleTooltip).toHaveStyle({ maxWidth: "15rem" })
152+
},
153+
{ timeout: 1000 },
154+
)
155+
})
156+
157+
it("should handle long content with text wrapping", async () => {
158+
const user = userEvent.setup()
159+
const longContent =
160+
"This is a very long tooltip content that should definitely wrap when displayed because it exceeds the maximum width constraint"
161+
162+
render(
163+
<TooltipProvider delayDuration={0}>
164+
<StandardTooltip content={longContent}>
165+
<button>Hover me</button>
166+
</StandardTooltip>
167+
</TooltipProvider>,
168+
)
169+
170+
const trigger = screen.getByText("Hover me")
171+
await user.hover(trigger)
172+
173+
await waitFor(
174+
() => {
175+
const tooltips = screen.getAllByText(longContent)
176+
const visibleTooltip = tooltips.find((el) => el.getAttribute("role") !== "tooltip")
177+
expect(visibleTooltip).toHaveClass("max-w-[300px]", "break-words")
178+
},
179+
{ timeout: 1000 },
180+
)
181+
})
182+
})

webview-ui/src/components/ui/standard-tooltip.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ interface StandardTooltipProps {
1818
className?: string
1919
/** Whether the trigger should be rendered as a child */
2020
asChild?: boolean
21+
/** Maximum width of the tooltip content */
22+
maxWidth?: number | string
2123
}
2224

2325
/**
@@ -46,11 +48,14 @@ export function StandardTooltip({
4648
sideOffset = 4,
4749
className,
4850
asChild = true,
51+
maxWidth,
4952
}: StandardTooltipProps) {
53+
const style = maxWidth ? { maxWidth: typeof maxWidth === "number" ? `${maxWidth}px` : maxWidth } : undefined
54+
5055
return (
5156
<Tooltip>
5257
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
53-
<TooltipContent side={side} align={align} sideOffset={sideOffset} className={className}>
58+
<TooltipContent side={side} align={align} sideOffset={sideOffset} className={className} style={style}>
5459
{content}
5560
</TooltipContent>
5661
</Tooltip>

webview-ui/src/components/ui/tooltip.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ const TooltipContent = React.forwardRef<
2020
collisionPadding={10}
2121
avoidCollisions={true}
2222
className={cn(
23-
"z-50 overflow-hidden rounded-xs bg-vscode-notifications-background border border-vscode-notifications-border px-3 py-1.5 text-xs text-vscode-notifications-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
23+
"z-50 rounded-xs bg-vscode-notifications-background border border-vscode-notifications-border px-3 py-1.5 text-xs text-vscode-notifications-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
24+
"max-w-[300px] break-words",
2425
className,
2526
)}
2627
{...props}

0 commit comments

Comments
 (0)