Skip to content

Commit 2d8211a

Browse files
authored
Add remark_breaks to handle newlines (#183)
1 parent 34cfd15 commit 2d8211a

File tree

4 files changed

+136
-0
lines changed

4 files changed

+136
-0
lines changed

apps/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"react-markdown": "^10.1.0",
6464
"react-use": "^17.6.0",
6565
"recharts": "^2.15.3",
66+
"remark-breaks": "^4.0.0",
6667
"require-in-the-middle": "^7.5.2",
6768
"shiki": "^3.7.0",
6869
"sonner": "^2.0.3",

apps/web/src/app/(authenticated)/usage/Messages.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useMemo, useEffect, useState } from 'react';
22
import ReactMarkdown from 'react-markdown';
3+
import remarkBreaks from 'remark-breaks';
34
import { Link2 } from 'lucide-react';
45
import { toast } from 'sonner';
56

@@ -312,6 +313,7 @@ export const Messages = ({
312313
) : (
313314
<div className="text-sm leading-relaxed markdown-prose">
314315
<ReactMarkdown
316+
remarkPlugins={[remarkBreaks]}
315317
components={{
316318
a: PlainTextLink,
317319
code: CodeBlock,
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { render, screen } from '@testing-library/react';
2+
import { vi, describe, it, expect } from 'vitest';
3+
import { Messages } from '@/app/(authenticated)/usage/Messages';
4+
import type { Message } from '@/actions/analytics/messages';
5+
6+
// Mock the hooks and dependencies
7+
vi.mock('@/hooks/useAutoScroll', () => ({
8+
useAutoScroll: () => ({
9+
containerRef: { current: null },
10+
scrollToBottom: vi.fn(),
11+
autoScrollToBottom: vi.fn(),
12+
userHasScrolled: false,
13+
}),
14+
}));
15+
16+
vi.mock('@/lib/formatters', () => ({
17+
formatTimestamp: (timestamp: number) => new Date(timestamp).toLocaleString(),
18+
}));
19+
20+
describe('Messages Component - Newline Handling', () => {
21+
const createMockMessage = (text: string, id = '1'): Message => ({
22+
id,
23+
orgId: null,
24+
userId: 'test-user',
25+
taskId: 'test-task',
26+
text,
27+
timestamp: Date.now(),
28+
ts: Date.now(),
29+
type: 'say',
30+
say: 'text',
31+
ask: null,
32+
mode: 'code',
33+
reasoning: null,
34+
partial: null,
35+
});
36+
37+
it('should render messages with newlines properly', () => {
38+
const messageWithNewlines = createMockMessage('Line 1\nLine 2\nLine 3');
39+
40+
render(<Messages messages={[messageWithNewlines]} />);
41+
42+
// The text should be present in the document
43+
expect(screen.getByText(/Line 1/)).toBeInTheDocument();
44+
expect(screen.getByText(/Line 2/)).toBeInTheDocument();
45+
expect(screen.getByText(/Line 3/)).toBeInTheDocument();
46+
});
47+
48+
it('should handle multiple consecutive newlines', () => {
49+
const messageWithMultipleNewlines = createMockMessage(
50+
'Paragraph 1\n\nParagraph 2\n\n\nParagraph 3',
51+
);
52+
53+
render(<Messages messages={[messageWithMultipleNewlines]} />);
54+
55+
expect(screen.getByText(/Paragraph 1/)).toBeInTheDocument();
56+
expect(screen.getByText(/Paragraph 2/)).toBeInTheDocument();
57+
expect(screen.getByText(/Paragraph 3/)).toBeInTheDocument();
58+
});
59+
60+
it('should handle mixed content with newlines', () => {
61+
const mixedContent = createMockMessage(
62+
'Regular text\n**Bold text**\n`code snippet`\nMore text',
63+
);
64+
65+
render(<Messages messages={[mixedContent]} />);
66+
67+
expect(screen.getByText(/Regular text/)).toBeInTheDocument();
68+
expect(screen.getByText(/Bold text/)).toBeInTheDocument();
69+
expect(screen.getByText(/code snippet/)).toBeInTheDocument();
70+
expect(screen.getByText(/More text/)).toBeInTheDocument();
71+
});
72+
73+
it('should handle command messages without markdown processing', () => {
74+
const commandMessage: Message = {
75+
id: '1',
76+
orgId: null,
77+
userId: 'test-user',
78+
taskId: 'test-task',
79+
text: 'npm install\ncd project\nls -la',
80+
timestamp: Date.now(),
81+
ts: Date.now(),
82+
type: 'ask',
83+
say: null,
84+
ask: 'command',
85+
mode: 'code',
86+
reasoning: null,
87+
partial: null,
88+
};
89+
90+
render(<Messages messages={[commandMessage]} />);
91+
92+
// Command messages should preserve newlines in the monospace container
93+
const commandContainer = screen.getByText(/npm install/);
94+
expect(commandContainer).toBeInTheDocument();
95+
expect(commandContainer.closest('.font-mono')).toBeInTheDocument();
96+
});
97+
});

pnpm-lock.yaml

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)