fix: Keep Untagged Chat Code Fences Plain (#17986)
Some checks are pending
Backend Unit Tests / Build packages (push) Waiting to run
Backend Unit Tests / Codegraph select (push) Waiting to run
Backend Unit Tests / TypeScript type checks (push) Blocked by required conditions
Backend Unit Tests / Circular dependency checks (push) Waiting to run
Backend Unit Tests / Tests: api (shard 1/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 2/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 3/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: data-provider (push) Blocked by required conditions
Backend Unit Tests / Tests: data-schemas (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 1/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 2/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 3/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 4/4) (push) Blocked by required conditions
Codegraph E2E Vote Gate / Gate (push) Waiting to run
Codegraph E2E Votes / vote (shard 1/10) (push) Waiting to run
Codegraph E2E Votes / vote (shard 10/10) (push) Waiting to run
Codegraph E2E Votes / vote (shard 2/10) (push) Waiting to run
Codegraph E2E Votes / vote (shard 3/10) (push) Waiting to run
Codegraph E2E Votes / vote (shard 4/10) (push) Waiting to run
Codegraph E2E Votes / vote (shard 5/10) (push) Waiting to run
Codegraph E2E Votes / vote (shard 6/10) (push) Waiting to run
Codegraph E2E Votes / vote (shard 7/10) (push) Waiting to run
Codegraph E2E Votes / vote (shard 8/10) (push) Waiting to run
Codegraph E2E Votes / vote (shard 9/10) (push) Waiting to run
Docker Dev Branch Images Build / publish (push) Waiting to run
Frontend Unit Tests / Client build recovery regression (push) Waiting to run
Frontend Unit Tests / Codegraph select (push) Waiting to run
Frontend Unit Tests / Build packages (push) Waiting to run
Frontend Unit Tests / TypeScript type checks (client) (push) Blocked by required conditions
Frontend Unit Tests / Tests: @librechat/chat (push) Blocked by required conditions
Frontend Unit Tests / Tests: @librechat/client (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 1/2) (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 2/2) (push) Blocked by required conditions
Frontend Unit Tests / Vite build verification (push) Blocked by required conditions

This commit is contained in:
Marco Beretta 2026-11-10 13:21:24 +01:00 • committed by GitHub
parent 317dd05a82
commit fbbf343286
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 111 additions and 3 deletions

View file

@ -68,7 +68,7 @@ class MarkdownErrorBoundary extends React.Component<
[
rehypeHighlight,
{
detect: true,
detect: false,
ignoreMissing: true,
subset: langSubset,
},

View file

@ -34,7 +34,7 @@ const MarkdownLite = memo(
[
rehypeHighlight,
{
detect: true,
detect: false,
ignoreMissing: true,
subset: langSubset,
},

View file

@ -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 }) => (
<code data-language={lang}>{codeChildren}</code>
),
}));
const WholeMessage = ({ content }: { content: string }) => (
<ArtifactProvider>
<CodeBlockProvider>
<ReactMarkdown
rehypePlugins={
getRehypePlugins() as React.ComponentProps<typeof ReactMarkdown>['rehypePlugins']
}
components={getMarkdownComponents()}
>
{content}
</ReactMarkdown>
</CodeBlockProvider>
</ArtifactProvider>
);
const Throw = (): never => {
throw new Error('Exercise markdown fallback');
};
const Fallback = ({
content,
codeExecution = true,
}: {
content: string;
codeExecution?: boolean;
}) => (
<MarkdownErrorBoundary content={content} codeExecution={codeExecution}>
<Throw />
</MarkdownErrorBoundary>
);
const renderers = [
['whole message', WholeMessage],
['blocks', (props: { content: string }) => <Markdown {...props} isLatestMessage={false} />],
['lite', MarkdownLite],
[
'lite without execution',
(props: { content: string }) => <MarkdownLite {...props} codeExecution={false} />,
],
['fallback', Fallback],
[
'fallback without execution',
(props: { content: string }) => <Fallback {...props} codeExecution={false} />,
],
] 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(
<QueryClientProvider client={new QueryClient()}>
<RecoilRoot>
<Renderer content={content} />
</RecoilRoot>
</QueryClientProvider>,
);
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;');
});
});

View file

@ -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 } => ({