mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
fix: prevent mobile markdown parser from stalling on unsupported blocks (#20313)
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { runInNewContext } from 'node:vm'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { normalizeMobileMarkdownPreviewHtml } from './mobile-markdown-preview-html'
|
||||
import { parseMobileMarkdown } from './mobile-markdown-parser'
|
||||
|
||||
function parseWithDeadline(input: string) {
|
||||
// A synchronous parser loop must fail without hanging the test worker.
|
||||
return runInNewContext('parse(input)', { parse: parseMobileMarkdown, input }, { timeout: 250 })
|
||||
}
|
||||
|
||||
describe('mobile Markdown parser progress', () => {
|
||||
it.each(['```c++', '```c#', '``` ts', '```ts title="file.ts"', '````', '```!'])(
|
||||
'retains unsupported fence %s as paragraph text',
|
||||
(fence) => {
|
||||
expect(parseWithDeadline(fence)).toEqual([{ type: 'paragraph', text: fence }])
|
||||
expect(parseWithDeadline(`before\n${fence}\nafter`)).toEqual([
|
||||
{ type: 'paragraph', text: `before\n${fence}\nafter` }
|
||||
])
|
||||
}
|
||||
)
|
||||
|
||||
it.each(['# ', '## ', '###### ', '#\t'])('consumes incomplete heading %s', (heading) => {
|
||||
expect(parseWithDeadline(`${heading}\nnext`)).toEqual([
|
||||
{ type: 'paragraph', text: `${heading}\nnext` }
|
||||
])
|
||||
})
|
||||
|
||||
it('handles unsupported fences through the production preview normalization', () => {
|
||||
const input = normalizeMobileMarkdownPreviewHtml('```c++\nint x = 1;')
|
||||
expect(parseWithDeadline(input)).toEqual([{ type: 'paragraph', text: input }])
|
||||
})
|
||||
|
||||
it('recognizes a supported fence after unsupported fence text', () => {
|
||||
expect(parseWithDeadline('```c++\n```ts\nconst x = 1\n```')).toEqual([
|
||||
{ type: 'paragraph', text: '```c++' },
|
||||
{ type: 'code', text: 'const x = 1', language: 'ts', closed: true }
|
||||
])
|
||||
})
|
||||
|
||||
it('preserves supported fences and their streaming state', () => {
|
||||
expect(parseWithDeadline('before\n```ts\nconst x = 1')).toEqual([
|
||||
{ type: 'paragraph', text: 'before' },
|
||||
{ type: 'code', text: 'const x = 1', language: 'ts', closed: false }
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -8,6 +8,9 @@ export type MobileMarkdownBlock =
|
||||
| { type: 'table'; headers: string[]; rows: string[][] }
|
||||
| { type: 'rule' }
|
||||
|
||||
const HEADING = /^(#{1,6})\s+(.+)$/
|
||||
const CODE_FENCE = /^```([A-Za-z0-9_-]+)?\s*$/
|
||||
|
||||
function splitTableRow(line: string): string[] {
|
||||
return line
|
||||
.trim()
|
||||
@@ -34,7 +37,7 @@ export function parseMobileMarkdown(content: string): MobileMarkdownBlock[] {
|
||||
continue
|
||||
}
|
||||
|
||||
const fence = line.match(/^```([A-Za-z0-9_-]+)?\s*$/)
|
||||
const fence = line.match(CODE_FENCE)
|
||||
if (fence) {
|
||||
index += 1
|
||||
const code: string[] = []
|
||||
@@ -80,7 +83,7 @@ export function parseMobileMarkdown(content: string): MobileMarkdownBlock[] {
|
||||
continue
|
||||
}
|
||||
|
||||
const heading = line.match(/^(#{1,6})\s+(.+)$/)
|
||||
const heading = line.match(HEADING)
|
||||
if (heading) {
|
||||
blocks.push({ type: 'heading', level: heading[1]!.length, text: heading[2]!.trim() })
|
||||
index += 1
|
||||
@@ -121,8 +124,8 @@ export function parseMobileMarkdown(content: string): MobileMarkdownBlock[] {
|
||||
while (
|
||||
index < lines.length &&
|
||||
lines[index]?.trim() &&
|
||||
!(lines[index] ?? '').startsWith('```') &&
|
||||
!/^(#{1,6})\s+/.test(lines[index] ?? '') &&
|
||||
!CODE_FENCE.test(lines[index] ?? '') &&
|
||||
!HEADING.test(lines[index] ?? '') &&
|
||||
!/^>\s?/.test(lines[index] ?? '') &&
|
||||
!/^\s*(?:[-*+]|\d+[.)])\s+/.test(lines[index] ?? '') &&
|
||||
!/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(lines[index] ?? '')
|
||||
|
||||
Reference in New Issue
Block a user