178 lines
5.3 KiB
TypeScript
178 lines
5.3 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
import {
|
|
MATH_BLOCK_TYPE,
|
|
MATH_INLINE_TYPE,
|
|
injectMathInBlocks,
|
|
preProcessMathMarkdown,
|
|
readCompletedInlineMathAtEnd,
|
|
serializeMathAwareBlocks,
|
|
} from './mathMarkdown'
|
|
|
|
describe('math markdown round-trip', () => {
|
|
it('injects inline math placeholders into BlockNote inline content', () => {
|
|
const preprocessed = preProcessMathMarkdown({ markdown: 'Energy is $E=mc^2$ in prose.' })
|
|
const blocks = [{
|
|
type: 'paragraph',
|
|
content: [{ type: 'text', text: preprocessed, styles: {} }],
|
|
children: [],
|
|
}]
|
|
|
|
const [block] = injectMathInBlocks(blocks) as Array<{ content: unknown[] }>
|
|
|
|
expect(block.content).toEqual([
|
|
{ type: 'text', text: 'Energy is ', styles: {} },
|
|
{ type: MATH_INLINE_TYPE, props: { latex: 'E=mc^2' }, content: undefined },
|
|
{ type: 'text', text: ' in prose.', styles: {} },
|
|
])
|
|
})
|
|
|
|
it('injects display math placeholders into dedicated math blocks', () => {
|
|
const preprocessed = preProcessMathMarkdown({ markdown: '$$\n\\int_0^1 x\\,dx\n$$' })
|
|
const blocks = [{
|
|
type: 'paragraph',
|
|
content: [{ type: 'text', text: preprocessed, styles: {} }],
|
|
children: [],
|
|
}]
|
|
|
|
const [block] = injectMathInBlocks(blocks) as Array<{ type: string; props: { latex: string } }>
|
|
|
|
expect(block.type).toBe(MATH_BLOCK_TYPE)
|
|
expect(block.props.latex).toBe('\\int_0^1 x\\,dx')
|
|
})
|
|
|
|
it('preprocesses single-line display math without rewinding later lines', () => {
|
|
const preprocessed = preProcessMathMarkdown({
|
|
markdown: 'Intro\n\n$$x^2$$\n\nDone',
|
|
})
|
|
const blocks = preprocessed.split('\n\n').map((text) => ({
|
|
type: 'paragraph',
|
|
content: [{ type: 'text', text, styles: {} }],
|
|
children: [],
|
|
}))
|
|
|
|
const [, block, afterBlock] = injectMathInBlocks(blocks) as Array<{
|
|
type: string
|
|
props?: { latex: string }
|
|
content?: Array<{ text: string }>
|
|
}>
|
|
|
|
expect(block.type).toBe(MATH_BLOCK_TYPE)
|
|
expect(block.props?.latex).toBe('x^2')
|
|
expect(afterBlock.content?.[0]?.text).toBe('Done')
|
|
})
|
|
|
|
it('serializes math nodes back to Markdown-compatible source', () => {
|
|
const editor = {
|
|
blocksToMarkdownLossy: vi.fn((blocks: unknown[]) => {
|
|
return (blocks as Array<{ content?: Array<{ text?: string }> }>)
|
|
.map((block) => block.content?.map((item) => item.text ?? '').join('') ?? '')
|
|
.join('\n\n')
|
|
}),
|
|
}
|
|
const blocks = [
|
|
{
|
|
type: 'paragraph',
|
|
content: [
|
|
{ type: 'text', text: 'Inline ', styles: {} },
|
|
{ type: MATH_INLINE_TYPE, props: { latex: 'a^2+b^2=c^2' } },
|
|
],
|
|
children: [],
|
|
},
|
|
{
|
|
type: MATH_BLOCK_TYPE,
|
|
props: { latex: '\\frac{1}{2}' },
|
|
children: [],
|
|
},
|
|
{
|
|
type: 'paragraph',
|
|
content: [{ type: 'text', text: 'Done', styles: {} }],
|
|
children: [],
|
|
},
|
|
]
|
|
|
|
expect(serializeMathAwareBlocks(editor, blocks)).toBe(
|
|
'Inline $a^2+b^2=c^2$\n\n$$\n\\frac{1}{2}\n$$\n\nDone',
|
|
)
|
|
})
|
|
|
|
it('round-trips inline math inside table cells', () => {
|
|
const tableCellMath = preProcessMathMarkdown({ markdown: '$a+b$' })
|
|
const blocks = [{
|
|
type: 'table',
|
|
content: {
|
|
type: 'tableContent',
|
|
rows: [{
|
|
cells: [{
|
|
type: 'tableCell',
|
|
content: [{ type: 'text', text: tableCellMath, styles: {} }],
|
|
}],
|
|
}],
|
|
},
|
|
children: [],
|
|
}]
|
|
|
|
const [tableBlock] = injectMathInBlocks(blocks) as Array<{
|
|
content: { rows: Array<{ cells: Array<{ content: unknown[] }> }> }
|
|
}>
|
|
|
|
expect(tableBlock.content.rows[0].cells[0].content).toEqual([{
|
|
type: MATH_INLINE_TYPE,
|
|
props: { latex: 'a+b' },
|
|
content: undefined,
|
|
}])
|
|
|
|
const editor = {
|
|
blocksToMarkdownLossy: vi.fn(() => '| Formula |\n| --- |\n| $a+b$ |'),
|
|
}
|
|
|
|
expect(serializeMathAwareBlocks(editor, [tableBlock])).toBe('| Formula |\n| --- |\n| $a+b$ |')
|
|
expect(editor.blocksToMarkdownLossy).toHaveBeenCalledWith([{
|
|
type: 'table',
|
|
content: {
|
|
type: 'tableContent',
|
|
rows: [{
|
|
cells: [{
|
|
type: 'tableCell',
|
|
content: [{ type: 'text', text: '$a+b$' }],
|
|
}],
|
|
}],
|
|
},
|
|
children: [],
|
|
}])
|
|
})
|
|
|
|
it('leaves display-style math inside table cells as Markdown source', () => {
|
|
expect(preProcessMathMarkdown({ markdown: '| Formula |\n| --- |\n| $$c$$ |' })).toBe(
|
|
'| Formula |\n| --- |\n| $$c$$ |',
|
|
)
|
|
})
|
|
|
|
it('leaves inline code and fenced code math-looking text untouched', () => {
|
|
const markdown = [
|
|
'Keep `$not_math$` literal.',
|
|
'',
|
|
'```',
|
|
'$$',
|
|
'x^2',
|
|
'$$',
|
|
'```',
|
|
].join('\n')
|
|
|
|
expect(preProcessMathMarkdown({ markdown })).toBe(markdown)
|
|
})
|
|
|
|
it('recognizes completed inline math at the end of text', () => {
|
|
expect(readCompletedInlineMathAtEnd({ text: 'Energy is $E=mc^2$' })).toEqual({
|
|
latex: 'E=mc^2',
|
|
start: 10,
|
|
end: 17,
|
|
})
|
|
})
|
|
|
|
it('ignores incomplete, escaped, and display-style dollar sequences at text end', () => {
|
|
expect(readCompletedInlineMathAtEnd({ text: 'Energy is $E=mc^2' })).toBeNull()
|
|
expect(readCompletedInlineMathAtEnd({ text: String.raw`Energy is $E=mc^2\$` })).toBeNull()
|
|
expect(readCompletedInlineMathAtEnd({ text: '$$x^2$$' })).toBeNull()
|
|
})
|
|
})
|