The remediation text is the payload - the enable-API console link was clipped by the one-line truncate. The scenario now asserts the text wraps un-clipped and the link sits inside the visible box.
Unified · Split
| @@ -304,7 +304,8 @@ scenario( · 303 unmodified lines | |||
| 304 | await step( | 304 | await step( |
| 305 | "The row carries Google's own instruction, console link included", | 305 | "The row carries Google's own instruction, console link included", |
| 306 | async () => { | 306 | async () => { |
| 307 | await connections.getByText(/has not been used in project/).waitFor(); | 307 | const detail = connections.getByText(/has not been used in project/); |
| 308 | await detail.waitFor(); | ||
| 308 | const consoleLink = connections.getByRole("link", { | 309 | const consoleLink = connections.getByRole("link", { |
| 309 | name: /console\.developers\.google\.com/, | 310 | name: /console\.developers\.google\.com/, |
| 310 | }); | 311 | }); |
| @@ -313,6 +314,22 @@ scenario( · 2 unmodified lines | |||
| 313 | await consoleLink.getAttribute("href"), | 314 | await consoleLink.getAttribute("href"), |
| 314 | "the enable-API console link is clickable", | 315 | "the enable-API console link is clickable", |
| 315 | ).toContain("console.developers.google.com"); | 316 | ).toContain("console.developers.google.com"); |
| 317 | // Visible in FULL, not just present in the DOM: a one-line | ||
| 318 | // truncate keeps the link in the tree while clipping it from | ||
| 319 | // view, which is exactly the regression this guards against. | ||
| 320 | // A clipped element overflows horizontally; a wrapped one | ||
| 321 | // grows taller instead. | ||
| 322 | const clipped = await detail.evaluate( | ||
| 323 | (el) => | ||
| 324 | el.scrollWidth > el.clientWidth + 1 || el.scrollHeight > el.clientHeight + 1, | ||
| 325 | ); | ||
| 326 | expect(clipped, "the remediation text wraps instead of clipping").toBe(false); | ||
| 327 | const linkVisible = await consoleLink.evaluate((el) => { | ||
| 328 | const rect = el.getBoundingClientRect(); | ||
| 329 | const detailRect = el.closest("p")?.getBoundingClientRect(); | ||
| 330 | return detailRect ? rect.right <= detailRect.right + 1 : false; | ||
| 331 | }); | ||
| 332 | expect(linkVisible, "the console link sits inside the visible detail box").toBe(true); | ||
| 316 | }, | 333 | }, |
| 317 | ); | 334 | ); |
| 318 | 335 | ||
| @@ -192,9 +192,13 @@ function AccountRow(props: { · 191 unmodified lines | |||
| 192 | </CardStackEntryDescription> | 192 | </CardStackEntryDescription> |
| 193 | ) : null} | 193 | ) : null} |
| 194 | {misconfigured && probe?.detail ? ( | 194 | {misconfigured && probe?.detail ? ( |
| 195 | <CardStackEntryDescription className="mt-1 text-xs text-muted-foreground"> | 195 | // Not CardStackEntryDescription: that truncates to one line, and this |
| 196 | // text IS the remediation (the enable-API console link must stay | ||
| 197 | // visible in full). Wrap instead; break anywhere so the long URL | ||
| 198 | // cannot overflow the row. | ||
| 199 | <p className="mt-1 whitespace-normal text-xs text-muted-foreground [overflow-wrap:anywhere]"> | ||
| 196 | <DetailWithLinks text={probe.detail} /> | 200 | <DetailWithLinks text={probe.detail} /> |
| 197 | </CardStackEntryDescription> | 201 | </p> |
| 198 | ) : null} | 202 | ) : null} |
| 199 | {needsReconsent ? ( | 203 | {needsReconsent ? ( |
| 200 | <CardStackEntryDescription className="mt-1 text-xs text-muted-foreground"> | 204 | <CardStackEntryDescription className="mt-1 text-xs text-muted-foreground"> |