Skip to content
Open
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
35 changes: 21 additions & 14 deletions packages/x-markdown/src/plugins/Mermaid/__test__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import React from 'react';
import Mermaid from '../index';

const getActionButtonByLabel = (label: string): HTMLElement => {
const icon = screen.getByLabelText(label);
const button = icon.closest('.ant-actions-item');
if (!button) throw new Error(`Action button with label '${label}' not found.`);
return button as HTMLElement;
};

// Mock mermaid
jest.mock('mermaid', () => ({
initialize: jest.fn(),
Expand Down Expand Up @@ -135,7 +142,7 @@ describe('Mermaid Plugin', () => {

render(<Mermaid>{mermaidContent}</Mermaid>);

const copyButton = screen.getByRole('button', { name: 'copy' });
const copyButton = getActionButtonByLabel('copy');
fireEvent.click(copyButton);

await waitFor(() => {
Expand All @@ -154,7 +161,7 @@ describe('Mermaid Plugin', () => {

render(<Mermaid>{mermaidContent}</Mermaid>);

const copyButton = screen.getByRole('button', { name: 'copy' });
const copyButton = getActionButtonByLabel('copy');

// ็กฎไฟ็‚นๅ‡ปไธไผšๆŠ›ๅ‡บ้”™่ฏฏ
expect(() => fireEvent.click(copyButton)).not.toThrow();
Expand All @@ -177,7 +184,7 @@ describe('Mermaid Plugin', () => {

render(<Mermaid>{mermaidContent}</Mermaid>);

const copyButton = screen.getByRole('button', { name: 'copy' });
const copyButton = getActionButtonByLabel('copy');
fireEvent.click(copyButton);

await waitFor(() => {
Expand All @@ -192,23 +199,23 @@ describe('Mermaid Plugin', () => {
it('should show zoom controls only in image mode', () => {
render(<Mermaid>{mermaidContent}</Mermaid>);

expect(screen.getByRole('button', { name: 'zoom-in' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'zoom-out' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Reset' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'download' })).toBeInTheDocument();
expect(screen.getByLabelText('zoom-in')).toBeInTheDocument();
expect(screen.getByLabelText('zoom-out')).toBeInTheDocument();
expect(screen.getByLabelText('undo')).toBeInTheDocument();
expect(screen.getByLabelText('download')).toBeInTheDocument();

const codeButton = screen.getByText('Code');
fireEvent.click(codeButton);

expect(screen.queryByRole('button', { name: 'zoom-in' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'zoom-out' })).not.toBeInTheDocument();
expect(screen.queryByLabelText('zoom-in')).not.toBeInTheDocument();
expect(screen.queryByLabelText('zoom-out')).not.toBeInTheDocument();
});

it('should handle zoom in/out', () => {
render(<Mermaid>{mermaidContent}</Mermaid>);

const zoomInButton = screen.getByRole('button', { name: 'zoom-in' });
const zoomOutButton = screen.getByRole('button', { name: 'zoom-out' });
const zoomInButton = getActionButtonByLabel('zoom-in');
const zoomOutButton = getActionButtonByLabel('zoom-out');

fireEvent.click(zoomInButton);
fireEvent.click(zoomOutButton);
Expand All @@ -217,7 +224,7 @@ describe('Mermaid Plugin', () => {
it('should handle reset functionality', () => {
render(<Mermaid>{mermaidContent}</Mermaid>);

const resetButton = screen.getByRole('button', { name: 'Reset' });
const resetButton = getActionButtonByLabel('undo');
fireEvent.click(resetButton);
});
});
Expand Down Expand Up @@ -500,7 +507,7 @@ describe('Mermaid Plugin', () => {
container.querySelector = mockQuerySelector;
}

const downloadButton = screen.getByRole('button', { name: 'download' });
const downloadButton = getActionButtonByLabel('download');
fireEvent.click(downloadButton);

// Wait for async operations
Expand Down Expand Up @@ -542,7 +549,7 @@ describe('Mermaid Plugin', () => {
container.querySelector = mockQuerySelector;
}

const downloadButton = screen.getByRole('button', { name: 'download' });
const downloadButton = getActionButtonByLabel('download');
fireEvent.click(downloadButton);

// Should not throw and should return early
Expand Down
86 changes: 51 additions & 35 deletions packages/x-markdown/src/plugins/Mermaid/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { CopyOutlined, DownloadOutlined, ZoomInOutlined, ZoomOutOutlined } from '@ant-design/icons';
import {
CopyOutlined,
DownloadOutlined,
UndoOutlined,
ZoomInOutlined,
ZoomOutOutlined,
} from '@ant-design/icons';
import useXComponentConfig from '@ant-design/x/es/_util/hooks/use-x-component-config';
import Actions from '@ant-design/x/es/actions';
import type { ItemType } from '@ant-design/x/es/actions/interface';
import useLocale from '@ant-design/x/es/locale/useLocale';
import useXProviderContext from '@ant-design/x/es/x-provider/hooks/use-x-provider-context';
import locale_EN from '@ant-design/x/locale/en_US';
import { Button, message, Segmented, Space, Tooltip } from 'antd';
import { message, Segmented } from 'antd';
import classnames from 'classnames';
import throttle from 'lodash.throttle';
import mermaid from 'mermaid';
Expand Down Expand Up @@ -222,6 +230,46 @@ const Mermaid: React.FC<MermaidProps> = React.memo((props) => {
}
};

// ============================ Action Items ============================
const baseItems: ItemType[] = [
{
key: 'copy',
icon: <CopyOutlined />,
label: contextLocale.copy,
onItemClick: handleCopyCode,
},
];

const imageItems: ItemType[] = [
...baseItems,
{
key: 'zoomIn',
icon: <ZoomInOutlined />,
label: contextLocale.zoomIn,
onItemClick: handleZoomIn,
},
{
key: 'zoomOut',
icon: <ZoomOutOutlined />,
label: contextLocale.zoomOut,
onItemClick: handleZoomOut,
},
{
key: 'zoomReset',
icon: <UndoOutlined />,
label: contextLocale.zoomReset,
onItemClick: handleReset,
},
{
key: 'download',
icon: <DownloadOutlined />,
label: contextLocale.download,
onItemClick: handleDownload,
},
];

const actionItems = renderType === RenderType.Image ? imageItems : baseItems;

const renderHeader = () => {
if (header === null) return null;
if (header) return header;
Expand All @@ -244,39 +292,7 @@ const Mermaid: React.FC<MermaidProps> = React.memo((props) => {
value={renderType}
onChange={setRenderType}
/>
<Space>
<Tooltip title={contextLocale.copy}>
<Button type="text" size="small" icon={<CopyOutlined />} onClick={handleCopyCode} />
</Tooltip>
{renderType === RenderType.Image ? (
<>
<Tooltip title={contextLocale.zoomOut}>
<Button type="text" size="small" icon={<ZoomInOutlined />} onClick={handleZoomIn} />
</Tooltip>
<Tooltip title={contextLocale.zoomIn}>
<Button
type="text"
size="small"
icon={<ZoomOutOutlined />}
onClick={handleZoomOut}
/>
</Tooltip>
<Tooltip title={contextLocale.zoomReset}>
<Button type="text" size="small" onClick={handleReset}>
{contextLocale.zoomReset}
</Button>
</Tooltip>
<Tooltip title={contextLocale.download}>
<Button
type="text"
size="small"
icon={<DownloadOutlined />}
onClick={handleDownload}
/>
</Tooltip>
</>
) : null}
</Space>
<Actions items={actionItems} variant="borderless" />
</div>
);
};
Expand Down
4 changes: 2 additions & 2 deletions packages/x/components/locale/zh_CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ const localeValues: Required<xLocale & xMarkdownLocale> = {
Mermaid: {
copySuccess: 'ๅคๅˆถๆˆๅŠŸ',
copy: 'ๅคๅˆถไปฃ็ ',
zoomIn: '็ผฉๅฐ',
zoomOut: 'ๆ”พๅคง',
zoomIn: 'ๆ”พๅคง',
zoomOut: '็ผฉๅฐ',
zoomReset: '้‡็ฝฎ',
download: 'ไธ‹่ฝฝ',
code: 'ไปฃ็ ',
Expand Down