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
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { ScenarioConfig } from '../scenario-runner.js';

const prompt = String.raw`请只输出以下 7 行测试文本,不要解释,不要编号,不要使用代码块,也不要改变任何字符。尤其要原样保留所有美元符号和反斜杠:
$x^2$ is valid math
it costs $5 and $10
$5-$10
literal \$x$
formula $x + \$5$
literal then math: \$$x^2$
math then literal: $x^2\$$`;

export default {
name: 'inline-math-escaped-dollar',
spawn: ['node', 'dist/cli.js', '--yolo', '--model', 'qwen3.7-max-2026-06-08'],
terminal: {
title: 'qwen-code inline math',
cwd: '../../..',
cols: 120,
rows: 32,
},
flow: [
{
type: prompt,
capture: 'inline-math-escaped-dollar.png',
},
{
sleep: 20000,
capture: 'inline-math-escaped-dollar-settled.png',
},
],
gif: false,
} satisfies ScenarioConfig;
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const markdownPrompt = `Output a compact Markdown rendering verification sample
4. Inline math $x$ and $y = \\\\frac{-b \\\\pm \\\\sqrt{b^2 - 4ac}}{2a}$.
5. One display math block using $$ fences.
6. One checked and one unchecked task list item.
7. Literal inline code \`$zz$\` and escaped math source \\\\$xy$.
7. Literal inline code \`$zz$\`, escaped prose \\\\$xy$, formula $x + \\\\$5$, and the boundaries \\\\$$x^2$ and $x^2\\\\$$.

Do not explain the sample.`;

Expand Down
25 changes: 25 additions & 0 deletions packages/cli/src/ui/commands/copyCommand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,31 @@ describe('copyCommand', () => {
});
});

it('should copy formulas containing or adjacent to escaped dollars', async () => {
if (!copyCommand.action) throw new Error('Command has no action');

mockGetHistoryShallow.mockReturnValue([
{
role: 'model',
parts: [
{
text: String.raw`literal \$x$
formula $x + \$5$
literal then math: \$$x^2$
math then literal: $x^2\$$`,
},
],
},
]);
mockCopyToClipboard.mockResolvedValue(undefined);

const expected = [String.raw`x + \$5`, 'x^2', String.raw`x^2\$`];
for (const [offset, expression] of expected.entries()) {
await copyCommand.action(mockContext, `inline-latex ${offset + 1}`);
expect(mockCopyToClipboard).toHaveBeenLastCalledWith(expression);
}
});

it('should copy a numbered inline LaTeX expression with /copy latex inline 1', async () => {
if (!copyCommand.action) throw new Error('Command has no action');

Expand Down
43 changes: 40 additions & 3 deletions packages/cli/src/ui/utils/InlineMarkdownRenderer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,44 @@ describe('<RenderInline />', () => {
expect(output).not.toContain('$x$');
});

it('preserves escaped inline math and inline code', () => {
it('renders escaped dollars as prose while keeping inline code verbatim', () => {
const { lastFrame } = renderWithProviders(
<RenderInline text={'Literal \\$xy$ and code `$xy$`'} enableInlineMath />,
<RenderInline
text={'Literal \\$xy$ and code `$xy$ and \\$xy$`'}
enableInlineMath
/>,
);

expect((lastFrame() ?? '').replace(/\n/g, '')).toContain(
'Literal \\$xy$ and code$xy$',
'Literal $xy$ and code$xy$ and \\$xy$',
);
});

it('renders escaped dollars inside and next to inline math', () => {
const text = String.raw`$x^2$ is valid math
it costs $5 and $10
$5-$10
literal \$x$
formula $x + \$5$
literal then math: \$$x^2$
math then literal: $x^2\$$`;
const { lastFrame } = renderWithProviders(
<RenderInline text={text} enableInlineMath />,
);

const output = (lastFrame() ?? '').replace(/\n/g, '');
expect(output).toContain('x² is valid math');
expect(output).toContain('it costs $5 and $10');
expect(output).toContain('$5-$10');
expect(output).toContain('literal $x$');
expect(output).toContain('x + $5');
expect(output).toContain('literal then math:');
expect(output).toContain('$x²');
expect(output).toContain('math then literal:');
expect(output).toContain('x²$');
expect(output).not.toContain('\\$');
});

it('keeps math literal inside multi-backtick code spans', () => {
const { lastFrame } = renderWithProviders(
<RenderInline text={'Use ``a `$xy$` b`` then $y$'} enableInlineMath />,
Expand All @@ -101,6 +129,15 @@ describe('<RenderInline />', () => {
expect(getPlainTextLength('code ``a `$xy$` b``', true)).toBe(
'code a `$xy$` b'.length,
);
expect(getPlainTextLength(String.raw`literal \$x$`, true)).toBe(
'literal $x$'.length,
);
expect(getPlainTextLength(String.raw`formula $x + \$5$`, true)).toBe(
'formula x + $5'.length,
);
expect(getPlainTextLength(String.raw`even \\$x$`, true)).toBe(
'even \\x'.length,
);
});

it('does not parse ordinary dollar amounts as inline math', () => {
Expand Down
Loading
Loading