fix(app): keep Mermaid controls usable in inline previews

This commit is contained in:
luke
2026-11-06 16:41:55 +08:00
parent 4281372bee
commit 81bc669e3a
2 changed files with 83 additions and 15 deletions
@@ -1,3 +1,4 @@
import { expect } from "@playwright/test";
import { test } from "../support/fixtures";
import {
closeDiagramFullscreen,
@@ -21,6 +22,38 @@ const MERMAID_DIAGRAM = [
"```",
].join("\n");
test("bounds a tall inline diagram", async ({ page }) => {
test.setTimeout(120_000);
const diagram = [
"```mermaid",
"flowchart TD",
...Array.from({ length: 30 }, (_, i) => `N${i} --> N${i + 1}`),
"```",
].join("\n");
const agent = await seedMockAgentWorkspace({
repoPrefix: "mermaid-tall-",
title: "Tall Mermaid",
featureValues: {
mockStreamingAssistantResponse: diagram,
mockStreamingAssistantIntervalMs: 10,
},
});
try {
await openAgentRoute(page, agent);
await requestDiagram(agent);
await waitForDiagramTurnToComplete(agent);
await expectCompletedDiagram(page, ["N0", "N30"]);
const viewport = page.getByTestId("mermaid-viewport").last();
expect((await viewport.boundingBox())!.height).toBe(480);
await page.screenshot({ path: test.info().outputPath("tall-preview.png") });
await openDiagramFullscreen(page);
await expectFullscreenDiagram(page, ["N0", "N30"]);
await closeDiagramFullscreen(page);
} finally {
await agent.cleanup();
}
});
test("opens a Mermaid diagram fullscreen and closes it again", async ({ page }) => {
test.setTimeout(120_000);
@@ -40,6 +73,37 @@ test("opens a Mermaid diagram fullscreen and closes it again", async ({ page })
await expectCompletedDiagram(page, ["Start", "Ship"]);
});
await test.step("Source toggles preserve the iframe and exclude hidden controls", async () => {
const viewport = page.getByTestId("mermaid-viewport").last();
const iframe = await viewport.locator("iframe").elementHandle();
for (let i = 0; i < 3; i++) {
await viewport.hover();
await page.getByRole("button", { name: "View source" }).last().click();
const viewDiagram = page.getByRole("button", { name: "View diagram" }).last();
if (i === 0) await page.screenshot({ path: test.info().outputPath("source.png") });
await viewDiagram.click();
await expect(viewDiagram).toHaveCount(0);
if (i === 0) await page.screenshot({ path: test.info().outputPath("diagram.png") });
await expect(page.getByTestId("mermaid-fullscreen-viewport")).toHaveCount(0);
expect(
await viewport.locator("iframe").evaluate((node, original) => node === original, iframe),
).toBe(true);
}
await viewport.hover();
await page.getByRole("button", { name: "View source" }).last().click();
await viewport.getByTestId("mermaid-fullscreen").focus();
await expect(viewport.getByTestId("mermaid-fullscreen")).not.toBeFocused();
await page.getByRole("button", { name: "View diagram" }).last().click();
const box = await viewport.boundingBox();
expect(box!.height).toBeGreaterThanOrEqual(56);
expect(box!.height).toBeLessThan(480);
await viewport.hover();
await page.getByRole("button", { name: "Zoom in" }).last().click();
await expect(page.getByRole("button", { name: "Reset view" }).last()).toBeEnabled();
await page.getByRole("button", { name: "Reset view" }).last().click();
await expect(page.getByRole("button", { name: "Reset view" }).last()).toBeDisabled();
});
await test.step("The toolbar opens the diagram fullscreen", async () => {
await openDiagramFullscreen(page);
await expectFullscreenDiagram(page, ["Start", "Review", "Ship"]);
@@ -72,7 +72,7 @@ function MermaidFenceHostImpl({
() => [
getDiagramBoxStyle(textStyle),
containerStyle,
{ height: runtimeHeight, minHeight: MIN_DIAGRAM_BOX_HEIGHT },
{ height: Math.min(runtimeHeight, MAX_PREVIEW_HEIGHT), minHeight: MIN_DIAGRAM_BOX_HEIGHT },
],
[runtimeHeight, textStyle],
);
@@ -111,20 +111,22 @@ function MermaidFenceHostImpl({
) : null}
</View>
) : null}
<ZoomableViewport
accessibilityLabel={t("message.diagram.diagram")}
actions={actions}
contentSize={diagramSize}
style={viewportStyle}
testID="mermaid-viewport"
wheelActivation="modifier"
>
<MermaidIframeRuntime
request={request}
onRendered={handleRendered}
onRenderFailed={renderFailed}
/>
</ZoomableViewport>
<div inert={!diagramVisible} style={boundaryStyle}>
<ZoomableViewport
accessibilityLabel={t("message.diagram.diagram")}
actions={actions}
contentSize={diagramSize}
style={viewportStyle}
testID="mermaid-viewport"
wheelActivation="modifier"
>
<MermaidIframeRuntime
request={request}
onRendered={handleRendered}
onRenderFailed={renderFailed}
/>
</ZoomableViewport>
</div>
{isFullscreen && visible ? (
<MermaidFullscreenViewer
source={visible.source}
@@ -142,6 +144,8 @@ const MEASURING_SIZE = { width: 240, height: 240 };
* with `overflow: hidden`, so a shorter box leaves the buttons half-clipped and unclickable.
*/
const MIN_DIAGRAM_BOX_HEIGHT = 56;
const MAX_PREVIEW_HEIGHT = 480;
const boundaryStyle: React.CSSProperties = { display: "contents" };
const sourceContainerStyle: ViewStyle = { position: "relative" };
/**
* The viewport's own root is `flex: 1`, so as a flex item it has `flex-basis: 0%` and that basis