Skip to content

Commit 96c3727

Browse files
committed
fix(webview): keep code and buttons visible in contrast themes
1 parent e6eebe5 commit 96c3727

13 files changed

Lines changed: 47 additions & 5 deletions

webview-ui/src/components/common/CodeBlock.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,15 +345,18 @@ const CodeBlock = memo(
345345

346346
// Store whether we should scroll after highlighting completes
347347
const shouldScrollAfterHighlightRef = useRef(false)
348+
const previousSourceRef = useRef(source)
348349

349350
// Check if we should scroll when source changes
350351
useEffect(() => {
351-
// Only set the flag if we're at the bottom when source changes
352-
if (preRef.current && source && !wasScrolledUpRef.current) {
352+
const sourceChanged = previousSourceRef.current !== source
353+
// Follow streaming updates when the user was already at the bottom, but keep initial content at the top.
354+
if (sourceChanged && preRef.current && source && !wasScrolledUpRef.current) {
353355
shouldScrollAfterHighlightRef.current = true
354356
} else {
355357
shouldScrollAfterHighlightRef.current = false
356358
}
359+
previousSourceRef.current = source
357360
}, [source])
358361

359362
const updateCodeBlockButtonPosition = useCallback((forceHide = false) => {

webview-ui/src/components/common/__tests__/CodeBlock.spec.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { render, screen, fireEvent, act, waitFor } from "@/utils/test-utils"
44

55
import CodeBlock, { getCodeBlockTheme } from "../CodeBlock"
6+
import { getHighlighter } from "../../../utils/highlighter"
67

78
// Mock the translation context
89
vi.mock("../../../i18n/TranslationContext", () => ({
@@ -136,6 +137,30 @@ describe("CodeBlock", () => {
136137
expect(screen.getByText(/const x = 1/)).toBeInTheDocument()
137138
})
138139

140+
it("starts at the top and follows later source updates when already at the bottom", async () => {
141+
const highlighter = await getHighlighter("typescript")
142+
let resolveHighlighter: (value: typeof highlighter) => void = () => undefined
143+
vi.mocked(getHighlighter).mockImplementationOnce(() => new Promise((resolve) => (resolveHighlighter = resolve)))
144+
145+
const { container, rerender } = render(<CodeBlock source="const first = 1;" language="typescript" />)
146+
const scroller = container.querySelector('[windowshade="true"]') as HTMLDivElement
147+
Object.defineProperties(scroller, {
148+
scrollHeight: { configurable: true, value: 600 },
149+
clientHeight: { configurable: true, value: 100 },
150+
})
151+
152+
await act(async () => resolveHighlighter(highlighter))
153+
await waitFor(() => expect(screen.getByText(/const first = 1/)).toBeInTheDocument())
154+
expect(scroller.scrollTop).toBe(0)
155+
156+
scroller.scrollTop = 500
157+
fireEvent.scroll(scroller)
158+
rerender(<CodeBlock source={"const first = 1;\nconst second = 2;"} language="typescript" />)
159+
160+
await waitFor(() => expect(screen.getByText(/const second = 2/)).toBeInTheDocument())
161+
await waitFor(() => expect(scroller.scrollTop).toBe(600))
162+
})
163+
139164
it("handles theme switching", async () => {
140165
const code = "const x = 1;"
141166
document.body.className = "light"

webview-ui/src/components/common/__tests__/RenderedContentContrast.visual.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ for (const theme of visualThemes) {
1717

1818
const code = component.getByTestId("code-block").locator("code")
1919
await expect(code).toContainText("Hello, Zoo Code", { timeout: 20_000 })
20+
const codeScroller = code.locator("xpath=../..")
21+
await expect.poll(() => codeScroller.evaluate((element) => element.scrollTop)).toBe(0)
2022
const codeBackground = component.getByTestId("code-block").locator("pre")
2123
const syntaxTokens = code.locator("span[style]")
2224
for (let index = 0; index < (await syntaxTokens.count()); index++) {
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ const buttonVariants = cva(
99
{
1010
variants: {
1111
variant: {
12-
primary: "bg-primary text-primary-foreground hover:bg-primary/70",
12+
primary:
13+
"border border-[var(--vscode-button-border,var(--vscode-contrastBorder,transparent))] bg-primary text-primary-foreground hover:bg-primary/70",
1314
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/70",
1415
ghost: "hover:bg-accent hover:text-accent-foreground",
1516
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",

webview-ui/src/components/welcome/__tests__/WelcomeViewProvider.visual.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,23 @@ for (const theme of visualThemes) {
1818

1919
const screen = component.locator(".fixed.inset-0")
2020
const heading = component.getByRole("heading", { level: 2 })
21+
const action = component.getByRole("button", { name: /provider/i }).first()
2122
await expect(heading).toBeVisible()
2223
await expectContrast(heading, { background: screen, label: `${theme.name} welcome heading` })
23-
await expectContrast(component.getByRole("button", { name: /provider/i }).first(), {
24-
background: component.getByRole("button", { name: /provider/i }).first(),
24+
await expectContrast(action, {
25+
background: action,
2526
label: `${theme.name} welcome action`,
2627
})
28+
if (theme.name.startsWith("high-contrast")) {
29+
await expect(action).toHaveCSS("border-top-width", "1px")
30+
await expect(action).toHaveCSS("border-top-style", "solid")
31+
await expectContrast(action, {
32+
background: screen,
33+
foregroundProperty: "border-color",
34+
minimum: 3,
35+
label: `${theme.name} welcome action boundary`,
36+
})
37+
}
2738

2839
await expect(screen).toHaveScreenshot(`welcome-screen-${theme.name}.png`)
2940
})
Binary file not shown.

0 commit comments

Comments
 (0)