From fbbf343286dd24fa34d112f36bdf755d25f09a46 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Tue, 10 Nov 2026 13:21:24 +0100 Subject: [PATCH] fix: Keep Untagged Chat Code Fences Plain (#17986) --- .../Content/MarkdownErrorBoundary.tsx | 2 +- .../Chat/Messages/Content/MarkdownLite.tsx | 2 +- .../__tests__/MarkdownHighlight.test.tsx | 108 ++++++++++++++++++ .../Chat/Messages/Content/markdownConfig.ts | 2 +- 4 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 client/src/components/Chat/Messages/Content/__tests__/MarkdownHighlight.test.tsx diff --git a/client/src/components/Chat/Messages/Content/MarkdownErrorBoundary.tsx b/client/src/components/Chat/Messages/Content/MarkdownErrorBoundary.tsx index 3d65a2ae14..02d9626e0d 100644 --- a/client/src/components/Chat/Messages/Content/MarkdownErrorBoundary.tsx +++ b/client/src/components/Chat/Messages/Content/MarkdownErrorBoundary.tsx @@ -68,7 +68,7 @@ class MarkdownErrorBoundary extends React.Component< [ rehypeHighlight, { - detect: true, + detect: false, ignoreMissing: true, subset: langSubset, }, diff --git a/client/src/components/Chat/Messages/Content/MarkdownLite.tsx b/client/src/components/Chat/Messages/Content/MarkdownLite.tsx index 54bd0eab31..4ff28dcd63 100644 --- a/client/src/components/Chat/Messages/Content/MarkdownLite.tsx +++ b/client/src/components/Chat/Messages/Content/MarkdownLite.tsx @@ -34,7 +34,7 @@ const MarkdownLite = memo( [ rehypeHighlight, { - detect: true, + detect: false, ignoreMissing: true, subset: langSubset, }, diff --git a/client/src/components/Chat/Messages/Content/__tests__/MarkdownHighlight.test.tsx b/client/src/components/Chat/Messages/Content/__tests__/MarkdownHighlight.test.tsx new file mode 100644 index 0000000000..6405b8f106 --- /dev/null +++ b/client/src/components/Chat/Messages/Content/__tests__/MarkdownHighlight.test.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import { RecoilRoot } from 'recoil'; +import ReactMarkdown from 'react-markdown'; +import { render } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { getRehypePlugins, getMarkdownComponents } from '../markdownConfig'; +import { ArtifactProvider, CodeBlockProvider } from '~/Providers'; +import MarkdownErrorBoundary from '../MarkdownErrorBoundary'; +import MarkdownLite from '../MarkdownLite'; +import Markdown from '../Markdown'; + +jest.mock('~/components/Messages/Content/CodeBlock', () => ({ + __esModule: true, + default: ({ lang, codeChildren }: { lang: string; codeChildren: React.ReactNode }) => ( + {codeChildren} + ), +})); + +const WholeMessage = ({ content }: { content: string }) => ( + + + ['rehypePlugins'] + } + components={getMarkdownComponents()} + > + {content} + + + +); + +const Throw = (): never => { + throw new Error('Exercise markdown fallback'); +}; + +const Fallback = ({ + content, + codeExecution = true, +}: { + content: string; + codeExecution?: boolean; +}) => ( + + + +); + +const renderers = [ + ['whole message', WholeMessage], + ['blocks', (props: { content: string }) => ], + ['lite', MarkdownLite], + [ + 'lite without execution', + (props: { content: string }) => , + ], + ['fallback', Fallback], + [ + 'fallback without execution', + (props: { content: string }) => , + ], +] as const; + +const prose = 'Pick up milk, eggs and bread on the way home. Call the dentist: Tuesday at 3.'; + +describe.each(renderers)('%s highlighting', (_name, Renderer) => { + beforeEach(() => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + + const renderContent = (content: string) => { + const { container } = render( + + + + + , + ); + return container.querySelector('code'); + }; + + it.each([ + ['', prose, true], + ['', prose, false], + ['', 'const answer = 42;', true], + ['text', prose, true], + ['plaintext', prose, true], + ['unknownlang', prose, true], + ])('keeps %s fences plain for %s (closed: %s)', (language, content, closed) => { + const code = renderContent(`\`\`\`${language}\n${content}\n${closed ? '```' : ''}`); + expect(code).toHaveAttribute('data-language', language || 'text'); + expect(code?.textContent).toBe(`${content}\n`); + expect(code?.querySelector('[class*="hljs-"]')).toBeNull(); + }); + + it('retains explicit Python highlighting', () => { + const code = renderContent('```python\nprint("hello")\n```'); + expect(code).toHaveAttribute('data-language', 'python'); + expect(code?.querySelector('[class*="hljs-"]')).not.toBeNull(); + }); + + it('keeps inline code inline', () => { + const code = renderContent('Use `const answer = 42;` here.'); + expect(code).not.toHaveAttribute('data-language'); + expect(code?.textContent).toBe('const answer = 42;'); + }); +}); diff --git a/client/src/components/Chat/Messages/Content/markdownConfig.ts b/client/src/components/Chat/Messages/Content/markdownConfig.ts index dbf0fa9700..db082d0207 100644 --- a/client/src/components/Chat/Messages/Content/markdownConfig.ts +++ b/client/src/components/Chat/Messages/Content/markdownConfig.ts @@ -65,7 +65,7 @@ export const getRemarkPlugins = (latexParsing = true): PluggableList => [ export const getRehypePlugins = (): PluggableList => [ [rehypeKatex], - [rehypeHighlight, { detect: true, ignoreMissing: true, subset: langSubset }], + [rehypeHighlight, { detect: false, ignoreMissing: true, subset: langSubset }], ]; export const getMarkdownComponents = (): { [nodeType: string]: ElementType } => ({