fix(ui): render untagged fenced code blocks as proper code blocks (#11653)

This commit is contained in:
Jeff Ramnani
2026-08-28 20:09:13 +00:00
committed by GitHub
parent ef0924a0a0
commit 8ae4e4ba02
2 changed files with 44 additions and 2 deletions
@@ -172,6 +172,41 @@ console.log('Hello, World!');
expect(screen.getByText('console.log()')).toBeInTheDocument();
});
});
it('renders untagged fenced code blocks (no language) through the same CodeBlock component as tagged blocks', async () => {
const plainTextBlock = [
'first line of plain text',
'second line of plain text',
'',
'line after a blank line',
].join('\n');
const content = ['```', plainTextBlock, '```'].join('\n');
const { container } = renderWithIntl(<MarkdownContent content={content} />);
await waitFor(() => {
expect(screen.getByText(/first line of plain text/)).toBeInTheDocument();
});
// There should be exactly one <pre> element wrapping exactly one <code>
// element that contains the entire multi-line block as a single node,
// rather than one <code>/<pre> pairing per line.
const preElements = container.querySelectorAll('pre');
expect(preElements).toHaveLength(1);
const codeElementsInsidePre = preElements[0].querySelectorAll('code');
expect(codeElementsInsidePre).toHaveLength(1);
expect(codeElementsInsidePre[0].textContent).toContain('first line of plain text');
expect(codeElementsInsidePre[0].textContent).toContain('line after a blank line');
// The block should NOT use the small single-line "inline code" badge
// style - that would indicate the bug regressed.
expect(codeElementsInsidePre[0].className).not.toContain('bg-inline-code');
// It should get the same hover "copy" button that tagged code blocks
// get, since it now renders through the shared CodeBlock component.
expect(preElements[0].querySelector('[data-testid="copy-icon"]')).toBeInTheDocument();
});
});
describe('Markdown Features', () => {
@@ -152,8 +152,15 @@ const MarkdownCode = memo(
ref: React.Ref<HTMLElement>
) {
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
<CodeBlock language={match[1]}>{String(children).replace(/\n$/, '')}</CodeBlock>
const codeContent = String(children ?? '');
// react-markdown gives untagged fenced blocks no language-xxx className,
// so they look like inline code here. Block-level content always ends with
// a trailing newline, which inline code spans can never contain.
const isBlockLevelCode = !inline && codeContent.endsWith('\n');
return isBlockLevelCode ? (
<CodeBlock language={match ? match[1] : 'text'}>{codeContent.replace(/\n$/, '')}</CodeBlock>
) : (
<code ref={ref} {...props} className="break-all bg-inline-code whitespace-pre-wrap font-mono">
{children}