test(ui): strengthen TeX parser boundaries
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
/** Extend upstream dollar-only math syntax with TeX delimiters while reusing its token vocabulary. */
|
||||||
|
|
||||||
import { factorySpace } from 'micromark-factory-space'
|
import { factorySpace } from 'micromark-factory-space'
|
||||||
import type {} from 'micromark-extension-math'
|
import type {} from 'micromark-extension-math'
|
||||||
import { markdownLineEnding } from 'micromark-util-character'
|
import { markdownLineEnding } from 'micromark-util-character'
|
||||||
@@ -11,8 +13,11 @@ interface RemarkProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const previousBackslash: Previous = function (code) {
|
const previousBackslash: Previous = function (code) {
|
||||||
/* v8 ignore next -- micromark calls previous after an event has been emitted. */
|
if (code !== codes.backslash) return true
|
||||||
return code !== codes.backslash || this.events.at(-1)?.[1].type === types.characterEscape
|
const tail = this.events.at(-1)
|
||||||
|
/* v8 ignore next -- a previous code necessarily has a preceding event. */
|
||||||
|
if (tail === undefined) return false
|
||||||
|
return tail[1].type === types.characterEscape
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenizeBackslashMathText: Tokenizer = function (effects, ok, nok) {
|
const tokenizeBackslashMathText: Tokenizer = function (effects, ok, nok) {
|
||||||
|
|||||||
@@ -203,6 +203,12 @@ describe('MarkdownText', () => {
|
|||||||
math: 1,
|
math: 1,
|
||||||
display: 0,
|
display: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: String.raw`\\\(x\)`,
|
||||||
|
math: 1,
|
||||||
|
display: 0,
|
||||||
|
value: 'x',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
source: '\\(\\frac{1}{5}\n+\\frac{1}{7}\\)',
|
source: '\\(\\frac{1}{5}\n+\\frac{1}{7}\\)',
|
||||||
math: 1,
|
math: 1,
|
||||||
@@ -238,31 +244,51 @@ describe('MarkdownText', () => {
|
|||||||
rendered.unmount()
|
rendered.unmount()
|
||||||
}
|
}
|
||||||
|
|
||||||
const literal = render(<MarkdownText text={'\\\\(x\\)\n\n\\[x\n\n$$x$$ trailing'} />)
|
const literal = render(<MarkdownText text={'\\\\(x\\)\n\n\\[x'} />)
|
||||||
expect(literal.container.querySelectorAll('.katex')).toHaveLength(1)
|
expect(literal.container.querySelectorAll('.katex')).toHaveLength(0)
|
||||||
expect(literal.container.querySelector('.katex-display')).toBeNull()
|
expect(literal.container.querySelector('.katex-display')).toBeNull()
|
||||||
expect(literal.container.textContent).toContain('[x')
|
expect(literal.container.textContent).toContain('[x')
|
||||||
expect(literal.container.textContent).toContain('xxx trailing')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('keeps ordinary dollar blocks and incomplete delimiter candidates parseable', () => {
|
it('keeps ordinary dollar blocks and incomplete delimiter candidates parseable', () => {
|
||||||
const sources = [
|
const cases = [
|
||||||
'$$\n\\theta\n$$',
|
{ source: '$$\n\\theta\n$$', math: 1, display: 1 },
|
||||||
'$$$\\theta$$$',
|
{ source: '$$$\\theta$$$', math: 1, display: 0 },
|
||||||
'$$a$b\nc',
|
{ source: '$$a$b\nc', math: 0, display: 0 },
|
||||||
' \\[\n \\theta\n \\]',
|
{ source: ' \\[\n \\theta\n \\]', math: 1, display: 1 },
|
||||||
'\\(\\theta',
|
{ source: '\\(\\theta', math: 0, display: 0 },
|
||||||
'\\[\n\\[',
|
{ source: String.raw`\(a\\)`, math: 0, display: 0 },
|
||||||
'> \\[\nnot a quoted continuation\n\\]',
|
{ source: '\\[\n\\[', math: 0, display: 0 },
|
||||||
|
{ source: '> \\[\nnot a quoted continuation\n\\]', math: 0, display: 0 },
|
||||||
]
|
]
|
||||||
|
|
||||||
for (const source of sources) {
|
for (const item of cases) {
|
||||||
const rendered = render(<MarkdownText text={source} />)
|
const rendered = render(<MarkdownText text={item.source} />)
|
||||||
|
expect(rendered.container.querySelectorAll('.katex')).toHaveLength(item.math)
|
||||||
|
expect(rendered.container.querySelectorAll('.katex-display')).toHaveLength(item.display)
|
||||||
expect(rendered.container.querySelector('.katex-error')).toBeNull()
|
expect(rendered.container.querySelector('.katex-error')).toBeNull()
|
||||||
rendered.unmount()
|
rendered.unmount()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('lets display math interrupt an open paragraph', () => {
|
||||||
|
for (const source of ['Prose line\n\\[x\\]', 'Prose line\n$$x$$']) {
|
||||||
|
const rendered = render(<MarkdownText text={source} />)
|
||||||
|
expect(rendered.container.querySelectorAll('p')).toHaveLength(1)
|
||||||
|
expect(rendered.container.querySelectorAll('.katex-display')).toHaveLength(1)
|
||||||
|
rendered.unmount()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('leaves a dollar block with trailing text to upstream inline math', () => {
|
||||||
|
const { container } = render(<MarkdownText text="$$x$$ trailing" />)
|
||||||
|
|
||||||
|
expect(container.querySelectorAll('.katex')).toHaveLength(1)
|
||||||
|
expect(container.querySelector('.katex-display')).toBeNull()
|
||||||
|
expect(container.querySelector('annotation')?.textContent).toBe('x')
|
||||||
|
expect(container.textContent).toContain('trailing')
|
||||||
|
})
|
||||||
|
|
||||||
it('renders escaped dollars and even backslash pairs before closing fences', () => {
|
it('renders escaped dollars and even backslash pairs before closing fences', () => {
|
||||||
const source = [
|
const source = [
|
||||||
String.raw`$$100\$$$`,
|
String.raw`$$100\$$$`,
|
||||||
|
|||||||
Reference in New Issue
Block a user