import { describe, expect, it } from 'vitest'
import { userEvent } from 'vitest/browser'
import { MATH_INPUT_REGEXP } from './math-inline-input-rule.ts'
import { setupTest } from './testing.ts'
describe('MATH_INPUT_REGEXP', () => {
const regexp = new RegExp(MATH_INPUT_REGEXP)
const cases: Array<
[input: string, delimiter: string | null, captured: string | null]
> = [
// Single dollar: inline math
['$x$', '$', 'x'],
['$x^2$', '$', 'x^2'],
['$a+b=c$', '$', 'a+b=c'],
[String.raw`$\alpha$`, '$', String.raw`\alpha`],
[String.raw`$\frac{a}{b}$`, '$', String.raw`\frac{a}{b}`],
['hello $x$', '$', 'x'],
['The formula $E=mc^2$', '$', 'E=mc^2'],
['$a + b$', '$', 'a + b'],
['$a$', '$', 'a'],
// Double dollar: block math
['$$x$$', '$$', 'x'],
['$$x^2$$', '$$', 'x^2'],
['$$a+b=c$$', '$$', 'a+b=c'],
[String.raw`$$\alpha$$`, '$$', String.raw`\alpha`],
['$$a + b$$', '$$', 'a + b'],
// Empty content — should not match
['$$', null, null],
['$$$$', null, null],
// Whitespace at boundaries — should not match
['$ x$', null, null],
['$x $', null, null],
['$ x $', null, null],
['$$ x$$', null, null],
['$$x $$', null, null],
// Mismatched delimiters — should not match
['$x$$', null, null],
['$$x$', null, null],
['$$$x=2$$', null, null],
// No dollar signs
['hello', null, null],
]
it.each(cases)(
'should handle %s',
(input, expectedDelimiter, expectedContent) => {
const match = regexp.exec(input)
const delimiter = match?.[1] ?? null
const captured = match?.[2] ?? null
expect(delimiter).toEqual(expectedDelimiter)
expect(captured).toEqual(expectedContent)
},
)
})
describe('defineMathInlineInputRule', () => {
const { editor, n } = setupTest()
it('should create mathInline when typing $...$', async () => {
editor.set(n.doc(n.p('')))
await userEvent.keyboard('$x^2')
expect(editor.view.state.doc.toJSON()).toEqual(n.doc(n.p('$x^2')).toJSON())
await userEvent.keyboard('$')
expect(editor.view.state.doc.toJSON()).toEqual(
n.doc(n.p(n.mathInline('x^2'))).toJSON(),
)
})
it('should create mathInline when typing $$...$$', async () => {
editor.set(n.doc(n.p('')))
await userEvent.keyboard('$$x^2$')
expect(editor.view.state.doc.toJSON()).toEqual(
n.doc(n.p('$$x^2$')).toJSON(),
)
await userEvent.keyboard('$')
expect(editor.view.state.doc.toJSON()).toEqual(
n.doc(n.p(n.mathInline('x^2'))).toJSON(),
)
})
it('should create mathInline with multi-character content', async () => {
editor.set(n.doc(n.p('')))
await userEvent.keyboard('$a+b=c$')
expect(editor.view.state.doc.toJSON()).toEqual(
n.doc(n.p(n.mathInline('a+b=c'))).toJSON(),
)
})
it('should not trigger with empty content $$', async () => {
editor.set(n.doc(n.p('')))
await userEvent.keyboard('$$')
expect(editor.view.state.doc.toJSON()).toEqual(n.doc(n.p('$$')).toJSON())
})
it('should not trigger with spaces at boundaries', async () => {
editor.set(n.doc(n.p('')))
await userEvent.keyboard('$ x^2 $')
expect(editor.view.state.doc.toJSON()).toEqual(
n.doc(n.p('$ x^2 $')).toJSON(),
)
})
it('should not trigger inside a code block', async () => {
editor.set(n.doc(n.codeBlock('')))
await userEvent.keyboard('$x^2$')
expect(editor.view.state.doc.toJSON()).toEqual(
n.doc(n.codeBlock('$x^2$')).toJSON(),
)
})
})