Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions apps/desktop/e2e/send-message.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { test, expect, COMPOSER_INPUT } from './fixtures';
import { FAKE_MERMAID_HOSTILE_PROMPT, FAKE_MERMAID_PROMPT } from '@maka/runtime';

/**
* Core chat loop: type a message, send it, see the deterministic fake backend
Expand Down Expand Up @@ -50,3 +51,136 @@ test('Enter mid-IME-composition commits the candidate instead of sending', async
await expect(page.getByText(/Fake backend received: 中文草稿 已提交/)).toBeVisible();
await expect(page.getByLabel('你发送的消息')).toHaveCount(1);
});

test('renders a settled Mermaid fence as a diagram', async ({ window: page }) => {
await page.setViewportSize({ width: 1440, height: 900 });
const composer = page.locator(COMPOSER_INPUT);
await composer.fill(FAKE_MERMAID_PROMPT);
await composer.press('Enter');

await expect(page.getByRole('button', { name: '重新生成' })).toBeVisible();
await expect(page.locator('.maka-bubble-streaming')).toHaveCount(0);
const diagram = page.locator('[data-maka-contract="mermaid"]').last();
await expect(diagram).toHaveAttribute('data-maka-mermaid-state', 'rendered');
await expect(diagram).toHaveAttribute('data-maka-mermaid-layout', 'ready');
await expect(diagram.locator('.maka-mermaid-svg > svg')).toBeVisible();
await expect(diagram.locator('script, foreignObject, a')).toHaveCount(0);
await expect(diagram.locator('.cluster')).toHaveCount(3);

const viewport = diagram.locator('.maka-mermaid-viewport');
const fitted = await viewport.evaluate((element) => ({
clientWidth: element.clientWidth,
scrollWidth: element.scrollWidth,
clientHeight: element.clientHeight,
scrollHeight: element.scrollHeight,
}));
expect(fitted.scrollWidth).toBeLessThanOrEqual(fitted.clientWidth + 1);
expect(fitted.scrollHeight).toBeLessThanOrEqual(fitted.clientHeight + 1);
await expect(viewport).toHaveCSS('touch-action', 'pan-y');

const viewSource = diagram.getByRole('button', { name: '查看 Mermaid 源码' });
await viewSource.click();
await expect(diagram.locator('.maka-mermaid-source')).toContainText('flowchart TB');
await viewSource.click();

const toolbar = diagram.locator('.maka-mermaid-toolbar');
const toolbarBeforeZoom = await toolbar.boundingBox();
const diagramBeforeZoom = await diagram.boundingBox();
const viewportHeightBeforeZoom = await viewport.evaluate((element) => element.getBoundingClientRect().height);
const zoomIn = diagram.getByRole('button', { name: '放大图表' });
await zoomIn.click();
await expect(diagram).toHaveAttribute('data-maka-mermaid-zoom', '1.25');
await zoomIn.click();
await expect(diagram).toHaveAttribute('data-maka-mermaid-zoom', '1.50');
const toolbarAfterZoom = await toolbar.boundingBox();
const diagramAfterZoom = await diagram.boundingBox();
const viewportHeightAfterZoom = await viewport.evaluate((element) => element.getBoundingClientRect().height);
const toolbarOffsetBeforeZoom = (toolbarBeforeZoom?.y ?? 0) - (diagramBeforeZoom?.y ?? 0);
const toolbarOffsetAfterZoom = (toolbarAfterZoom?.y ?? 0) - (diagramAfterZoom?.y ?? 0);
expect(Math.abs(toolbarOffsetAfterZoom - toolbarOffsetBeforeZoom)).toBeLessThanOrEqual(1);
expect(Math.abs(viewportHeightAfterZoom - viewportHeightBeforeZoom)).toBeLessThanOrEqual(1);
await expect.poll(() => viewport.evaluate((element) =>
element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight)).toBe(true);
const zoomedBounds = await diagram.evaluate((element) => {
const svg = element.querySelector('.maka-mermaid-svg > svg');
const content = svg?.querySelector('g');
const svgRect = svg?.getBoundingClientRect();
const contentRect = content?.getBoundingClientRect();
return {
svg: svgRect
? { top: svgRect.top, bottom: svgRect.bottom, width: svgRect.width, height: svgRect.height }
: null,
content: contentRect
? { top: contentRect.top, bottom: contentRect.bottom }
: null,
};
});
expect(zoomedBounds.svg).not.toBeNull();
expect(zoomedBounds.content).not.toBeNull();
expect(zoomedBounds.content?.top ?? 0).toBeGreaterThanOrEqual((zoomedBounds.svg?.top ?? 0) - 1);
expect(zoomedBounds.content?.bottom ?? 0).toBeLessThanOrEqual((zoomedBounds.svg?.bottom ?? 0) + 1);
await diagram.getByRole('button', { name: '适应视窗' }).click();
await expect(diagram).toHaveAttribute('data-maka-mermaid-zoom', '1.00');
const inlineSvg = diagram.locator('.maka-mermaid-svg > svg');
await expect(inlineSvg).toBeVisible();
await expect.poll(async () => {
const bounds = await inlineSvg.boundingBox();
return bounds ? bounds.width * bounds.height : 0;
}).toBeGreaterThan(0);
const inlineSvgBounds = await inlineSvg.boundingBox();
expect(inlineSvgBounds).not.toBeNull();

await diagram.getByRole('button', { name: '全屏查看图表' }).click();
const modal = page.locator('dialog.maka-mermaid-dialog');
await expect(modal).toHaveAttribute('open', '');
await expect(modal).toHaveAttribute('aria-modal', 'true');
const expandedDiagram = modal.locator('[data-maka-contract="mermaid"]');
const pageSize = page.viewportSize();
await expect.poll(async () => {
const bounds = await modal.boundingBox();
return bounds && pageSize
? Math.max(
Math.abs(bounds.x),
Math.abs(bounds.y),
Math.abs(bounds.width - pageSize.width),
Math.abs(bounds.height - pageSize.height),
)
: Number.POSITIVE_INFINITY;
}).toBeLessThanOrEqual(1);
await expect.poll(async () => {
const bounds = await expandedDiagram.locator('.maka-mermaid-svg > svg').boundingBox();
return bounds ? bounds.width * bounds.height : 0;
}).toBeGreaterThan((inlineSvgBounds?.width ?? 0) * (inlineSvgBounds?.height ?? 0) * 1.5);
await expect.poll(() => expandedDiagram.locator('.maka-mermaid-actions').evaluate((element) =>
getComputedStyle(element).getPropertyValue('-webkit-app-region'))).toBe('no-drag');
const exitFullscreen = expandedDiagram.getByRole('button', { name: '退出全屏图表' });
await expect(exitFullscreen).toBeFocused();
await page.keyboard.press('Tab');
await expect.poll(() => modal.evaluate((element) => element.contains(document.activeElement))).toBe(true);
await exitFullscreen.click();
await expect(modal).not.toHaveAttribute('open', '');
const enterFullscreen = diagram.getByRole('button', { name: '全屏查看图表' });
await expect(enterFullscreen).toBeFocused();

await enterFullscreen.click();
await expect(expandedDiagram.getByRole('button', { name: '退出全屏图表' })).toBeFocused();
await page.keyboard.press('Escape');
await expect(diagram.getByRole('button', { name: '全屏查看图表' })).toBeFocused();

await page.setViewportSize({ width: 340, height: 900 });
await expect(diagram.getByRole('button', { name: '全屏查看图表' })).toBeVisible();
await expect(diagram.getByRole('button', { name: '放大图表' })).toBeHidden();
});

test('keeps hostile Mermaid directives inert', async ({ window: page }) => {
const composer = page.locator(COMPOSER_INPUT);
await composer.fill(FAKE_MERMAID_HOSTILE_PROMPT);
await composer.press('Enter');

await expect(page.getByRole('button', { name: '重新生成' })).toBeVisible();
const diagram = page.locator('[data-maka-contract="mermaid"]').last();
await expect(diagram).toHaveAttribute('data-maka-mermaid-state', 'rendered');
await expect(diagram.locator('.maka-mermaid-svg > svg')).toBeVisible();
await expect(diagram.locator('script, foreignObject, a')).toHaveCount(0);
await expect(diagram.locator('[onclick], [onerror], [onload], [href^="javascript:"]')).toHaveCount(0);
});
Loading
Loading