Add markdown rendering to TTC error toast, with component tests (#7170)

Closes #5792. I tried to move these over to our new component test
bucket, but Remark doesn't play nice with that testing setup at least in
my initial attempts.
This commit is contained in:
Frank Noirot
2025-05-22 13:00:21 -04:00
committed by GitHub
parent ae569b61db
commit 2c7701e2d4
2 changed files with 110 additions and 3 deletions

View File

@ -0,0 +1,87 @@
import { render, screen } from '@testing-library/react'
import { ToastTextToCadError } from '@src/components/ToastTextToCad'
describe('ToastTextToCadError tests', () => {
const testData = {
errorWithMarkdown: `422 Unprocessable Entity. Text-to-CAD is still improving, and some prompts may fail. Try adjusting your prompt for better results. We review failures to enhance the model over time. For prompt tips and best practices, check out our community on [Discord](https://discord.gg/JQEpHR7Nt2) or [Discourse](https://community.zoo.dev).`,
errorWithPlainText: "I'M A TEAPOT",
errorWithMalformedContent: `[bad-link)(h:/ya;lk3&"& th] WHICH ## Heading? *** benches bj;lkj,,,{]<ul>((()))bhwlqq!`,
} as const
test('Happy path: renders markdown just fine', () => {
render(
<ToastTextToCadError
toastId="test"
newProjectName="newProject"
projectName="currProject"
method="whyDoWePassThisIn"
prompt="Something complex like a tiger or a tree"
message={testData.errorWithMarkdown}
key="testKey"
/>
)
// Locators and other constants
const editPromptButton = screen.getByRole('button', {
name: /edit prompt/i,
})
const dismissButton = screen.getByRole('button', { name: /dismiss/i })
// If an actual link is shown the renderer worked
const errorLink = screen.getByRole('link', { name: /discourse/i })
expect(editPromptButton).toBeVisible()
expect(dismissButton).toBeVisible()
expect(errorLink).toBeVisible()
})
test('Happy path: renders plaintext just fine', () => {
render(
<ToastTextToCadError
toastId="test"
newProjectName="newProject"
projectName="currProject"
method="whyDoWePassThisIn"
prompt="Something complex like a tiger or a tree"
message={testData.errorWithPlainText}
key="testKey"
/>
)
// Locators and other constants
const editPromptButton = screen.getByRole('button', {
name: /edit prompt/i,
})
const dismissButton = screen.getByRole('button', { name: /dismiss/i })
const errorParagraph = screen.getByText('TEAPOT', { exact: false })
expect(editPromptButton).toBeVisible()
expect(dismissButton).toBeVisible()
expect(errorParagraph).toBeVisible()
})
test('Happy path: renders malformed text just fine', () => {
render(
<ToastTextToCadError
toastId="test"
newProjectName="newProject"
projectName="currProject"
method="whyDoWePassThisIn"
prompt="Something complex like a tiger or a tree"
message={testData.errorWithMalformedContent}
key="testKey"
/>
)
// Locators and other constants
const editPromptButton = screen.getByRole('button', {
name: /edit prompt/i,
})
const dismissButton = screen.getByRole('button', { name: /dismiss/i })
// If it renders the malformed link as a paragraph that's good
const errorParagraph = screen.queryByText(/bad-link/, { exact: false })
expect(editPromptButton).toBeVisible()
expect(dismissButton).toBeVisible()
expect(errorParagraph).toBeVisible()
})
})

View File

@ -41,6 +41,13 @@ import {
import { commandBarActor } from '@src/lib/singletons'
import type { FileMeta } from '@src/lib/types'
import type { RequestedKCLFile } from '@src/machines/systemIO/utils'
import {
Marked,
type MarkedOptions,
escape,
unescape,
} from '@ts-stack/markdown'
import { SafeRenderer } from '@src/lib/markdown'
const CANVAS_SIZE = 128
const PROMPT_TRUNCATE_LENGTH = 128
@ -99,13 +106,26 @@ export function ToastTextToCadError({
projectName: string
newProjectName: string
}) {
const markedOptions: MarkedOptions = {
gfm: true,
breaks: true,
sanitize: true,
unescape,
escape,
}
return (
<div className="flex flex-col justify-between gap-6">
<section>
<h2>Text-to-CAD failed</h2>
<p className="text-sm text-chalkboard-70 dark:text-chalkboard-30">
{message}
</p>
<div
className="parsed-markdown mt-4 text-sm text-chalkboard-70 dark:text-chalkboard-30 max-h-60 overflow-y-auto whitespace-normal"
dangerouslySetInnerHTML={{
__html: Marked.parse(message, {
renderer: new SafeRenderer(markedOptions),
...markedOptions,
}),
}}
/>
</section>
<div className="flex justify-between gap-8">
<ActionButton