mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix: restore list styles for markdown lists in chat interface (#6095)
Co-authored-by: Roo Code <roomote@roocode.com> Co-authored-by: Daniel Riccio <ricciodaniel98@gmail.com>
This commit is contained in:
parent
c68f8f591e
commit
c61bd95bbe
4 changed files with 298 additions and 0 deletions
168
apps/vscode-e2e/src/suite/markdown-lists.test.ts
Normal file
168
apps/vscode-e2e/src/suite/markdown-lists.test.ts
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
import * as assert from "assert"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { waitUntilCompleted } from "./utils"
|
||||
import { setDefaultSuiteTimeout } from "./test-utils"
|
||||
|
||||
suite("Markdown List Rendering", function () {
|
||||
setDefaultSuiteTimeout(this)
|
||||
|
||||
test("Should render unordered lists with bullets in chat", async () => {
|
||||
const api = globalThis.api
|
||||
|
||||
const messages: ClineMessage[] = []
|
||||
|
||||
api.on("message", ({ message }: { message: ClineMessage }) => {
|
||||
if (message.type === "say" && message.partial === false) {
|
||||
messages.push(message)
|
||||
}
|
||||
})
|
||||
|
||||
const taskId = await api.startNewTask({
|
||||
configuration: { mode: "ask", alwaysAllowModeSwitch: true, autoApprovalEnabled: true },
|
||||
text: "Please show me an example of an unordered list with the following items: Apple, Banana, Orange",
|
||||
})
|
||||
|
||||
await waitUntilCompleted({ api, taskId })
|
||||
|
||||
// Find the message containing the list
|
||||
const listMessage = messages.find(
|
||||
({ say, text }) =>
|
||||
(say === "completion_result" || say === "text") &&
|
||||
text?.includes("Apple") &&
|
||||
text?.includes("Banana") &&
|
||||
text?.includes("Orange"),
|
||||
)
|
||||
|
||||
assert.ok(listMessage, "Should have a message containing the list items")
|
||||
|
||||
// The rendered markdown should contain list markers
|
||||
const messageText = listMessage?.text || ""
|
||||
assert.ok(
|
||||
messageText.includes("- Apple") || messageText.includes("* Apple") || messageText.includes("• Apple"),
|
||||
"List items should be rendered with bullet points",
|
||||
)
|
||||
})
|
||||
|
||||
test("Should render ordered lists with numbers in chat", async () => {
|
||||
const api = globalThis.api
|
||||
|
||||
const messages: ClineMessage[] = []
|
||||
|
||||
api.on("message", ({ message }: { message: ClineMessage }) => {
|
||||
if (message.type === "say" && message.partial === false) {
|
||||
messages.push(message)
|
||||
}
|
||||
})
|
||||
|
||||
const taskId = await api.startNewTask({
|
||||
configuration: { mode: "ask", alwaysAllowModeSwitch: true, autoApprovalEnabled: true },
|
||||
text: "Please show me a numbered list with three steps: First step, Second step, Third step",
|
||||
})
|
||||
|
||||
await waitUntilCompleted({ api, taskId })
|
||||
|
||||
// Find the message containing the numbered list
|
||||
const listMessage = messages.find(
|
||||
({ say, text }) =>
|
||||
(say === "completion_result" || say === "text") &&
|
||||
text?.includes("First step") &&
|
||||
text?.includes("Second step") &&
|
||||
text?.includes("Third step"),
|
||||
)
|
||||
|
||||
assert.ok(listMessage, "Should have a message containing the numbered list")
|
||||
|
||||
// The rendered markdown should contain numbered markers
|
||||
const messageText = listMessage?.text || ""
|
||||
assert.ok(
|
||||
messageText.includes("1. First step") || messageText.includes("1) First step"),
|
||||
"List items should be rendered with numbers",
|
||||
)
|
||||
})
|
||||
|
||||
test("Should render nested lists with proper hierarchy", async () => {
|
||||
const api = globalThis.api
|
||||
|
||||
const messages: ClineMessage[] = []
|
||||
|
||||
api.on("message", ({ message }: { message: ClineMessage }) => {
|
||||
if (message.type === "say" && message.partial === false) {
|
||||
messages.push(message)
|
||||
}
|
||||
})
|
||||
|
||||
const taskId = await api.startNewTask({
|
||||
configuration: { mode: "ask", alwaysAllowModeSwitch: true, autoApprovalEnabled: true },
|
||||
text: "Please create a nested list with 'Main item' having two sub-items: 'Sub-item A' and 'Sub-item B'",
|
||||
})
|
||||
|
||||
await waitUntilCompleted({ api, taskId })
|
||||
|
||||
// Find the message containing the nested list
|
||||
const listMessage = messages.find(
|
||||
({ say, text }) =>
|
||||
(say === "completion_result" || say === "text") &&
|
||||
text?.includes("Main item") &&
|
||||
text?.includes("Sub-item A") &&
|
||||
text?.includes("Sub-item B"),
|
||||
)
|
||||
|
||||
assert.ok(listMessage, "Should have a message containing the nested list")
|
||||
|
||||
// The rendered markdown should show hierarchy through indentation
|
||||
const messageText = listMessage?.text || ""
|
||||
|
||||
// Check for main item
|
||||
assert.ok(
|
||||
messageText.includes("- Main item") ||
|
||||
messageText.includes("* Main item") ||
|
||||
messageText.includes("• Main item"),
|
||||
"Main list item should be rendered",
|
||||
)
|
||||
|
||||
// Check for sub-items with indentation (typically 2-4 spaces or a tab)
|
||||
assert.ok(
|
||||
messageText.match(/\s{2,}- Sub-item A/) ||
|
||||
messageText.match(/\s{2,}\* Sub-item A/) ||
|
||||
messageText.match(/\s{2,}• Sub-item A/) ||
|
||||
messageText.includes("\t- Sub-item A") ||
|
||||
messageText.includes("\t* Sub-item A") ||
|
||||
messageText.includes("\t• Sub-item A"),
|
||||
"Sub-items should be indented",
|
||||
)
|
||||
})
|
||||
|
||||
test("Should render mixed ordered and unordered lists", async () => {
|
||||
const api = globalThis.api
|
||||
|
||||
const messages: ClineMessage[] = []
|
||||
|
||||
api.on("message", ({ message }: { message: ClineMessage }) => {
|
||||
if (message.type === "say" && message.partial === false) {
|
||||
messages.push(message)
|
||||
}
|
||||
})
|
||||
|
||||
const taskId = await api.startNewTask({
|
||||
configuration: { mode: "ask", alwaysAllowModeSwitch: true, autoApprovalEnabled: true },
|
||||
text: "Please create a list that has both numbered items and bullet points, mixing ordered and unordered lists",
|
||||
})
|
||||
|
||||
await waitUntilCompleted({ api, taskId })
|
||||
|
||||
// Find a message that contains both types of lists
|
||||
const listMessage = messages.find(
|
||||
({ say, text }) =>
|
||||
(say === "completion_result" || say === "text") &&
|
||||
text &&
|
||||
// Check for numbered list markers
|
||||
(text.includes("1.") || text.includes("1)")) &&
|
||||
// Check for bullet list markers
|
||||
(text.includes("-") || text.includes("*") || text.includes("•")),
|
||||
)
|
||||
|
||||
assert.ok(listMessage, "Should have a message containing mixed list types")
|
||||
})
|
||||
})
|
||||
|
|
@ -151,6 +151,31 @@ const StyledMarkdown = styled.div`
|
|||
margin-left: 0;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
/* Nested list styles */
|
||||
ul ul {
|
||||
list-style-type: circle;
|
||||
}
|
||||
|
||||
ul ul ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
|
||||
ol ol {
|
||||
list-style-type: lower-alpha;
|
||||
}
|
||||
|
||||
ol ol ol {
|
||||
list-style-type: lower-roman;
|
||||
}
|
||||
|
||||
p {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,4 +36,84 @@ describe("MarkdownBlock", () => {
|
|||
const paragraph = container.querySelector("p")
|
||||
expect(paragraph?.textContent).toBe("Check out this link: https://example.com.")
|
||||
})
|
||||
|
||||
it("should render unordered lists with proper styling", async () => {
|
||||
const markdown = `Here are some items:
|
||||
- First item
|
||||
- Second item
|
||||
- Nested item
|
||||
- Another nested item`
|
||||
|
||||
const { container } = render(<MarkdownBlock markdown={markdown} />)
|
||||
|
||||
// Wait for the content to be processed
|
||||
await screen.findByText(/Here are some items/, { exact: false })
|
||||
|
||||
// Check that ul elements exist
|
||||
const ulElements = container.querySelectorAll("ul")
|
||||
expect(ulElements.length).toBeGreaterThan(0)
|
||||
|
||||
// Check that list items exist
|
||||
const liElements = container.querySelectorAll("li")
|
||||
expect(liElements.length).toBe(4)
|
||||
|
||||
// Verify the text content
|
||||
expect(screen.getByText("First item")).toBeInTheDocument()
|
||||
expect(screen.getByText("Second item")).toBeInTheDocument()
|
||||
expect(screen.getByText("Nested item")).toBeInTheDocument()
|
||||
expect(screen.getByText("Another nested item")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should render ordered lists with proper styling", async () => {
|
||||
const markdown = `And a numbered list:
|
||||
1. Step one
|
||||
2. Step two
|
||||
3. Step three`
|
||||
|
||||
const { container } = render(<MarkdownBlock markdown={markdown} />)
|
||||
|
||||
// Wait for the content to be processed
|
||||
await screen.findByText(/And a numbered list/, { exact: false })
|
||||
|
||||
// Check that ol elements exist
|
||||
const olElements = container.querySelectorAll("ol")
|
||||
expect(olElements.length).toBe(1)
|
||||
|
||||
// Check that list items exist
|
||||
const liElements = container.querySelectorAll("li")
|
||||
expect(liElements.length).toBe(3)
|
||||
|
||||
// Verify the text content
|
||||
expect(screen.getByText("Step one")).toBeInTheDocument()
|
||||
expect(screen.getByText("Step two")).toBeInTheDocument()
|
||||
expect(screen.getByText("Step three")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should render nested lists with proper hierarchy", async () => {
|
||||
const markdown = `Complex list:
|
||||
1. First level ordered
|
||||
- Second level unordered
|
||||
- Another second level
|
||||
1. Third level ordered
|
||||
2. Another third level
|
||||
2. Back to first level`
|
||||
|
||||
const { container } = render(<MarkdownBlock markdown={markdown} />)
|
||||
|
||||
// Wait for the content to be processed
|
||||
await screen.findByText(/Complex list/, { exact: false })
|
||||
|
||||
// Check nested structure
|
||||
const olElements = container.querySelectorAll("ol")
|
||||
const ulElements = container.querySelectorAll("ul")
|
||||
|
||||
expect(olElements.length).toBeGreaterThan(0)
|
||||
expect(ulElements.length).toBeGreaterThan(0)
|
||||
|
||||
// Verify all text is rendered
|
||||
expect(screen.getByText("First level ordered")).toBeInTheDocument()
|
||||
expect(screen.getByText("Second level unordered")).toBeInTheDocument()
|
||||
expect(screen.getByText("Third level ordered")).toBeInTheDocument()
|
||||
expect(screen.getByText("Back to first level")).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -32,6 +32,31 @@ export const StyledMarkdown = styled.div`
|
|||
margin-left: 0;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
/* Nested list styles */
|
||||
ul ul {
|
||||
list-style-type: circle;
|
||||
}
|
||||
|
||||
ul ul ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
|
||||
ol ol {
|
||||
list-style-type: lower-alpha;
|
||||
}
|
||||
|
||||
ol ol ol {
|
||||
list-style-type: lower-roman;
|
||||
}
|
||||
|
||||
p {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue