Skip to content

Commit c4dc156

Browse files
committed
feat(math): enhance normalization of backslashes and improve test coverage
1 parent 1584dcc commit c4dc156

File tree

6 files changed

+142
-80
lines changed

6 files changed

+142
-80
lines changed

playground/src/const/markdown.ts

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,23 @@ export const streamContent = `>>>I'll create a simple Electron + Vue chat applic
3232
3333
## 1. 泰勒公式(Taylor's Formula)
3434
35-
### 一般形式(在点 \\(x = a\\) 处展开):
35+
### 一般形式(在点 \(x = a\) 处展开):
3636
\\[
37-
f(x) = f(a) + f'(a)(x-a) + \\frac{f''(a)}{2!}(x-a)^2 + \\frac{f'''(a)}{3!}(x-a)^3 + \\cdots + \\frac{f^{(n)}(a)}{n!}(x-a)^n + R_n(x)
37+
f(x) = f(a) + f'(a)(x-a) + \frac{f''(a)}{2!}(x-a)^2 + \frac{f'''(a)}{3!}(x-a)^3 + \cdots + \frac{f^{(n)}(a)}{n!}(x-a)^n + R_n(x)
3838
\\]
3939
4040
其中:
41-
- \\(f^{(k)}(a)\\) 是 \\(f(x)\\) 在 \\(x=a\\) 处的 \\(k\\) 阶导数
42-
- \\(R_n(x)\\) 是余项,常见形式有拉格朗日余项:
41+
- \(f^{(k)}(a)\) 是 \(f(x)\) 在 \(x=a\) 处的 \(k\) 阶导数
42+
- \(R_n(x)\) 是余项,常见形式有拉格朗日余项:
4343
\\[
44-
R_n(x) = \\frac{f^{(n+1)}(\\xi)}{(n+1)!}(x-a)^{n+1}, \\quad \\xi \\text{ 在 } a \\text{ 和 } x \\text{ 之间}
44+
R_n(x) = \frac{f^{(n+1)}(xi)}{(n+1)!}(x-a)^{n+1}, \quad xi \text{ 在 } a \text{ 和 } x \text{ 之间}
4545
\\]
4646
4747
---
4848
49-
## 2. 麦克劳林公式(Maclaurin's Formula,即 \\(a=0\\) 时的泰勒公式):
49+
## 2. 麦克劳林公式(Maclaurin's Formula,即 \(a=0\) 时的泰勒公式):
5050
\\[
51-
f(x) = f(0) + f'(0)x + \\frac{f''(0)}{2!}x^2 + \\frac{f'''(0)}{3!}x^3 + \\cdots + \\frac{f^{(n)}(0)}{n!}x^n + R_n(x)
51+
f(x) = f(0) + f'(0)x + \frac{f''(0)}{2!}x^2 + \frac{f'''(0)}{3!}x^3 + \cdots + \frac{f^{(n)}(0)}{n!}x^n + R_n(x)
5252
\\]
5353
5454
---
@@ -57,37 +57,35 @@ f(x) = f(0) + f'(0)x + \\frac{f''(0)}{2!}x^2 + \\frac{f'''(0)}{3!}x^3 + \\cdots
5757
5858
- **指数函数**:
5959
\\[
60-
e^x = 1 + x + \\frac{x^2}{2!} + \\frac{x^3}{3!} + \\cdots + \\frac{x^n}{n!} + \\cdots, \\quad x \\in \\mathbb{R}
60+
e^x = 1 + x + \frac{x^2}{2!} + \frac{x^3}{3!} + \cdots + \frac{x^n}{n!} + \cdots, \quad x \in \mathbb{R}
6161
\\]
6262
6363
- **正弦函数**:
6464
\\[
65-
\\sin x = x - \\frac{x^3}{3!} + \\frac{x^5}{5!} - \\frac{x^7}{7!} + \\cdots + (-1)^n \\frac{x^{2n+1}}{(2n+1)!} + \\cdots
65+
\\sin x = x - \frac{x^3}{3!} + \frac{x^5}{5!} - \frac{x^7}{7!} + \cdots + (-1)^n \frac{x^{2n+1}}{(2n+1)!} + \cdots
6666
\\]
6767
6868
- **余弦函数**:
6969
\\[
70-
\\cos x = 1 - \\frac{x^2}{2!} + \\frac{x^4}{4!} - \\frac{x^6}{6!} + \\cdots + (-1)^n \\frac{x^{2n}}{(2n)!} + \\cdots
70+
\cos x = 1 - \frac{x^2}{2!} + \frac{x^4}{4!} - \frac{x^6}{6!} + \cdots + (-1)^n \frac{x^{2n}}{(2n)!} + \cdots
7171
\\]
7272
73-
- **自然对数**(在 \\(x=0\\) 附近):
73+
- **自然对数**(在 \(x=0\) 附近):
7474
\\[
75-
\\ln(1+x) = x - \\frac{x^2}{2} + \\frac{x^3}{3} - \\frac{x^4}{4} + \\cdots + (-1)^{n-1} \\frac{x^n}{n} + \\cdots, \\quad -1 < x \\le 1
75+
\ln(1+x) = x - \frac{x^2}{2} + \frac{x^3}{3} - \frac{x^4}{4} + \cdots + (-1)^{n-1} \frac{x^n}{n} + \cdots, \quad -1 < x \le 1
7676
\\]
7777
78-
- **二项式展开**(\\( (1+x)^m \\),\\(m\\) 为实数):
78+
- **二项式展开**(\( (1+x)^m \),\(m\) 为实数):
7979
\\[
80-
(1+x)^m = 1 + mx + \\frac{m(m-1)}{2!}x^2 + \\frac{m(m-1)(m-2)}{3!}x^3 + \\cdots, \\quad |x| < 1
80+
(1+x)^m = 1 + mx + \frac{m(m-1)}{2!}x^2 + \frac{m(m-1)(m-2)}{3!}x^3 + \cdots, \quad |x| < 1
8181
\\]
8282
8383
- **公式**
84-
\[
85-
\text{付费转化率} = \\left( \\frac{\text{付费用户数}}{\text{月活用户数}} \\right) \times 100\%
86-
\]
84+
8785
8886
- **代入数据**
8987
\[
90-
\\frac{363}{15,\!135} \times 100\% = 2.398\%
88+
\frac{363}{15,\!135} \times 100\% = 2.398\%
9189
\]
9290
9391
- **计算工具验证**
@@ -414,4 +412,31 @@ graph TD
414412
Orphee_Lam_Tao -.->|追求| Lacus_Clyne
415413
\`\`\`
416414
415+
416+
### 1. **理解 \(\boldsymbol{\alpha}^T \boldsymbol{\beta} = 0\) 的含义**
417+
- \(\boldsymbol{\alpha}\) 和 \(\boldsymbol{\beta}\) 是三维列向量,因此 \(\boldsymbol{\alpha}^T \boldsymbol{\beta}\) 表示它们的点积(内积)。
418+
- \(\boldsymbol{\alpha}^T \boldsymbol{\beta} = 0\) 意味着向量 \(\boldsymbol{\alpha}\) 和 \(\boldsymbol{\beta}\) 正交(即垂直),因为点积为零表示它们之间的夹角为 90 度。
419+
420+
### 2. **正交补空间的概念**
421+
- 在线性代数中,对于一个子空间 \(W\),它的正交补空间(记为 \(W^\perp\))定义为所有与 \(W\) 中每个向量正交的向量的集合。即:
422+
\[
423+
W^\perp = \{ \mathbf{v} \in \mathbb{R}^3 \mid \mathbf{v} \cdot \mathbf{w} = 0 \text{ 对于所有 } \mathbf{w} \in W \}
424+
\]
425+
- 例如,如果 \(W\) 是由一个向量 \(\boldsymbol{\alpha}\) 张成的一维子空间(即 \(W = \operatorname{span}\{\boldsymbol{\alpha}\}\)),那么 \(W^\perp\) 就是所有与 \(\boldsymbol{\alpha}\) 正交的向量构成的二维平面。
426+
427+
### 3. **\(\boldsymbol{\alpha}^T \boldsymbol{\beta} = 0\) 与正交补空间的联系**
428+
- 当 \(\boldsymbol{\alpha}^T \boldsymbol{\beta} = 0\) 时,这意味着:
429+
- \(\boldsymbol{\beta}\) 属于 \(\operatorname{span}\\{\boldsymbol{\alpha}\\}\) 的正交补空间,即 \(\boldsymbol{\beta} \in (\operatorname{span}\{\boldsymbol{\alpha}\})^\perp\)。
430+
- 同样,\(\boldsymbol{\alpha}\) 也属于 \(\operatorname{span}\{\boldsymbol{\beta}\}\) 的正交补空间,即 \(\boldsymbol{\alpha} \in (\operatorname{span}\{\boldsymbol{\beta}\})^\perp\)。
431+
- 换句话说,\(\boldsymbol{\beta}\) 与 \(\boldsymbol{\alpha}\) 张成的直线正交,因此 \(\boldsymbol{\beta}\) 位于该直线的垂直平面(即正交补空间)上。反之亦然。
432+
433+
### 4. **在三维空间中的几何意义**
434+
- 在三维空间中,如果 \(\boldsymbol{\alpha}\) 是一个非零向量,那么 \(\operatorname{span}\{\boldsymbol{\alpha}\}\) 是一条通过原点的直线,而它的正交补空间 \((\operatorname{span}\{\boldsymbol{\alpha}\})^\perp\) 是一个通过原点且与该直线垂直的平面。
435+
- \(\boldsymbol{\alpha}^T \boldsymbol{\beta} = 0\) 表示 \(\boldsymbol{\beta}\) 位于这个垂直平面上。同样,如果 \(\boldsymbol{\beta}\) 非零,那么 \(\boldsymbol{\alpha}\) 也位于与 \(\boldsymbol{\beta}\) 垂直的平面上。
436+
437+
### 5. **推广到更一般的情况**
438+
- 如果考虑多个向量,正交补空间的概念可以扩展。例如,如果有一组向量 \(\{\boldsymbol{\alpha}_1, \boldsymbol{\alpha}_2, \ldots, \boldsymbol{\alpha}_k\}\),那么它们的张成子空间 \(W = \operatorname{span}\{\boldsymbol{\alpha}_1, \ldots, \boldsymbol{\alpha}_k\}\) 的正交补空间 \(W^\perp\) 包含所有与这些向量正交的向量。
439+
- 在这种情况下,\(\boldsymbol{\alpha}^T \boldsymbol{\beta} = 0\) 可以看作 \(\boldsymbol{\beta}\) 与 \(W\) 正交的一个特例(当 \(W\) 只由 \(\boldsymbol{\alpha}\) 张成时)。
440+
441+
总之,\(\boldsymbol{\alpha}^T \boldsymbol{\beta} = 0\) 直接体现了正交补空间的关系:它表明一个向量属于另一个向量张成子空间的正交补空间。如果你有更多向量或子空间,这种联系可以进一步深化。
417442
`

src/utils/markdown-parser/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ export function parseMarkdownToStructure(
2222
md: MarkdownIt,
2323
): ParsedNode[] {
2424
// Ensure markdown is a string — guard against null/undefined inputs from callers
25-
const safeMarkdown = (markdown ?? '').toString()
25+
const safeMarkdown = (markdown ?? '').toString().replace(/\right/g, '\\right')
26+
2627
// Get tokens from markdown-it
2728
const tokens = md.parse(safeMarkdown, {}) as MarkdownToken[]
2829
// Defensive: ensure tokens is an array

src/utils/markdown/plugins/math.ts

Lines changed: 64 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type MarkdownIt from 'markdown-it'
2-
// Exported helper for direct testing and reuse
32
import type { MathOptions } from '../config'
43

54
import findMatchingClose from '../findMatchingClose'
@@ -37,6 +36,9 @@ export const ESCAPED_TEX_BRACE_COMMANDS = TEX_BRACE_COMMANDS.map(c => c.replace(
3736
// Common KaTeX/TeX command names that might lose their leading backslash.
3837
// Keep this list conservative to avoid false-positives in normal text.
3938
export const KATEX_COMMANDS = [
39+
'ldots',
40+
'cdots',
41+
'quad',
4042
'in',
4143
'infty',
4244
'perp',
@@ -76,6 +78,10 @@ export const KATEX_COMMANDS = [
7678
'exp',
7779
'lim',
7880
'frac',
81+
'text',
82+
'left',
83+
'right',
84+
'times',
7985
]
8086

8187
// Precompute escaped KATEX commands and default regex used by
@@ -89,10 +95,6 @@ export const ESCAPED_KATEX_COMMANDS = KATEX_COMMANDS
8995
.map(c => c.replace(/[.*+?^${}()|[\\]\\\]/g, '\\$&'))
9096
.join('|')
9197
const CONTROL_CHARS_CLASS = '[\t\r\b\f\v]'
92-
// Match when command words appear at start, after whitespace, or after a
93-
// non-word (but not after a backslash). This avoids matching inside words
94-
// like "sin" (we only want to match " in" -> "\\in" when separated).
95-
const DEFAULT_KATEX_RE = new RegExp('(^|\\s|[^\\\\\\w])(' + `(?:${ESCAPED_KATEX_COMMANDS})\\b|${CONTROL_CHARS_CLASS}` + ')', 'g')
9698

9799
// Precompiled regexes for isMathLike to avoid reconstructing them per-call
98100
const TEX_CMD_RE = /\\[a-z]+/i
@@ -166,22 +168,29 @@ export function normalizeStandaloneBackslashT(s: string, opts?: MathOptions) {
166168
const escapeExclamation = opts?.escapeExclamation ?? true
167169

168170
// Choose a prebuilt regex when using default command set for performance,
169-
// otherwise build one from the provided commands.
170-
const re = (opts?.commands == null)
171-
? DEFAULT_KATEX_RE
172-
: new RegExp('(^|\\s|[^\\\\\\w])(' + `(?:${commands.slice().sort((a, b) => b.length - a.length).map(c => c.replace(/[.*+?^${}()|[\\]\\\]/g, '\\$&')).join('|')})\\b|${CONTROL_CHARS_CLASS}` + ')', 'g')
173-
174-
let out = s.replace(re, (_m, p1, p2) => {
175-
// If p2 is a control character, map it to its escaped letter (t, r, ...)
176-
if (controlMap[p2] !== undefined) {
177-
return `${p1}\\${controlMap[p2]}`
178-
}
179-
180-
// Otherwise if it's one of the katex command words, prefix with backslash
181-
if (commands.includes(p2))
182-
return `${p1}\\${p2}`
183-
184-
return _m
171+
// otherwise build one from the provided commands. Use a negative
172+
// lookbehind to ensure the matched command isn't already escaped (i.e.
173+
// not preceded by a backslash) and not part of a larger word. We also
174+
// match literal control characters (tab, backspace, etc.). This form
175+
// avoids capturing the prefix (p1) which previously caused overlapping
176+
// replacement issues.
177+
const commandPattern = (opts?.commands == null)
178+
? `(?:${ESCAPED_KATEX_COMMANDS})`
179+
: `(?:${commands.slice().sort((a, b) => b.length - a.length).map(c => c.replace(/[.*+?^${}()|[\\]\\"\]/g, '\\$&')).join('|')})`
180+
181+
// Match either a control character or an unescaped command word.
182+
const re = new RegExp(`${CONTROL_CHARS_CLASS}|(?<!\\\\|\\w)(${commandPattern})\\b`, 'g')
183+
184+
let out = s.replace(re, (m, cmd) => {
185+
// If m is a literal control character (e.g. '\t' as actual tab), map it.
186+
if (controlMap[m] !== undefined)
187+
return `\\${controlMap[m]}`
188+
189+
// Otherwise cmd will be populated with the matched command word.
190+
if (cmd && commands.includes(cmd))
191+
return `\\${cmd}`
192+
193+
return m
185194
})
186195

187196
// Escape standalone '!' but don't double-escape already escaped ones.
@@ -192,8 +201,12 @@ export function normalizeStandaloneBackslashT(s: string, opts?: MathOptions) {
192201
// lost their leading backslash, e.g. "operatorname{span}". Ensure we
193202
// restore a backslash before known brace-taking commands when they are
194203
// followed by '{' and are not already escaped.
195-
// Use default escaped list when possible.
196-
const braceEscaped = (opts?.commands == null) ? ESCAPED_KATEX_COMMANDS : commands.map(c => c.replace(/[.*+?^${}()|[\\]\\\]/g, '\\$&')).join('|')
204+
// Use default escaped list when possible. Include TEX_BRACE_COMMANDS so
205+
// known brace-taking TeX commands (e.g. `text`, `boldsymbol`) are also
206+
// restored when their leading backslash was lost.
207+
const braceEscaped = (opts?.commands == null)
208+
? [ESCAPED_TEX_BRACE_COMMANDS, ESCAPED_KATEX_COMMANDS].filter(Boolean).join('|')
209+
: [commands.map(c => c.replace(/[.*+?^${}()|[\\]\\\]/g, '\\$&')).join('|'), ESCAPED_TEX_BRACE_COMMANDS].filter(Boolean).join('|')
197210
if (braceEscaped) {
198211
const braceCmdRe = new RegExp(`(^|[^\\\\])(${braceEscaped})\\s*\\{`, 'g')
199212
out = out.replace(braceCmdRe, (_m, p1, p2) => `${p1}\\${p2}{`)
@@ -203,18 +216,20 @@ export function normalizeStandaloneBackslashT(s: string, opts?: MathOptions) {
203216
export function applyMath(md: MarkdownIt, mathOpts?: MathOptions) {
204217
// Inline rule for \(...\) and $$...$$ and $...$
205218
const mathInline = (state: any, silent: boolean) => {
219+
if (state.src.includes('\n')) {
220+
return false
221+
}
206222
const delimiters: [string, string][] = [
207223
['$$', '$$'],
224+
['\(', '\)'],
208225
['\\(', '\\)'],
209226
]
210227
let searchPos = 0
211228
// use findMatchingClose from util
212-
213229
for (const [open, close] of delimiters) {
214230
// We'll scan the entire inline source and tokenize all occurrences
215231
const src = state.src
216232
let foundAny = false
217-
218233
const pushText = (text: string) => {
219234
if (!text)
220235
return
@@ -243,7 +258,18 @@ export function applyMath(md: MarkdownIt, mathOpts?: MathOptions) {
243258
const index = src.indexOf(open, searchPos)
244259
if (index === -1)
245260
break
246-
261+
// If the delimiter is immediately preceded by a ']' (possibly with
262+
// intervening spaces), it's likely part of a markdown link like
263+
// `[text](...)`, so we should not treat this '(' as the start of
264+
// an inline math span. Also guard the index to avoid OOB access.
265+
if (index > 0) {
266+
let i = index - 1
267+
// skip spaces between ']' and the delimiter
268+
while (i >= 0 && src[i] === ' ')
269+
i--
270+
if (i >= 0 && src[i] === ']')
271+
return false
272+
}
247273
// 有可能遇到 \((\operatorname{span}\\{\boldsymbol{\alpha}\\})^\perp\)
248274
// 这种情况,前面的 \( 是数学公式的开始,后面的 ( 是普通括号
249275
// endIndex 需要找到与 open 对应的 close
@@ -281,10 +307,10 @@ export function applyMath(md: MarkdownIt, mathOpts?: MathOptions) {
281307
return c
282308
}
283309

284-
let isStrongPrefix = false
285-
const toPushBefore = prevConsumed ? src.slice(searchPos, index) : before
286-
if (countUnescapedStrong(toPushBefore) % 2 === 1) {
287-
isStrongPrefix = true
310+
let toPushBefore = prevConsumed ? src.slice(searchPos, index) : before
311+
const isStrongPrefix = countUnescapedStrong(toPushBefore) % 2 === 1
312+
if (index !== state.pos && isStrongPrefix) {
313+
toPushBefore = src.slice(state.pos, index)
288314
}
289315

290316
// strong prefix handling (preserve previous behavior)
@@ -342,8 +368,6 @@ export function applyMath(md: MarkdownIt, mathOpts?: MathOptions) {
342368
endLine: number,
343369
silent: boolean,
344370
) => {
345-
if (silent)
346-
return true
347371
const delimiters: [string, string][] = [
348372
['\\[', '\\]'],
349373
['$$', '$$'],
@@ -367,8 +391,7 @@ export function applyMath(md: MarkdownIt, mathOpts?: MathOptions) {
367391
nextLineStart,
368392
state.eMarks[startLine + 1],
369393
)
370-
const hasMathContent = isMathLike(nextLineText)
371-
if (hasMathContent) {
394+
if (isMathLike(nextLineText.trim())) {
372395
matched = true
373396
openDelim = open
374397
closeDelim = close
@@ -389,6 +412,9 @@ export function applyMath(md: MarkdownIt, mathOpts?: MathOptions) {
389412

390413
if (!matched)
391414
return false
415+
if (silent)
416+
return true
417+
392418
if (
393419
lineText.includes(closeDelim)
394420
&& lineText.indexOf(closeDelim) > openDelim.length
@@ -403,13 +429,8 @@ export function applyMath(md: MarkdownIt, mathOpts?: MathOptions) {
403429
endDelimIndex,
404430
)
405431

406-
// For the heuristic-only bracket delimiter '[', check content is math-like
407-
if (openDelim === '[' && !isMathLike(content))
408-
return false
409-
410432
const token: any = state.push('math_block', 'math', 0)
411-
412-
token.content = normalizeStandaloneBackslashT(content, mathOpts) // 规范化 \t -> \\\t
433+
token.content = normalizeStandaloneBackslashT(content)
413434
token.markup
414435
= openDelim === '$$' ? '$$' : openDelim === '[' ? '[]' : '\\[\\]'
415436
token.map = [startLine, startLine + 1]
@@ -437,9 +458,9 @@ export function applyMath(md: MarkdownIt, mathOpts?: MathOptions) {
437458
content = firstLineContent
438459

439460
for (nextLine = startLine + 1; nextLine < endLine; nextLine++) {
440-
const lineStart = state.bMarks[nextLine] + state.tShift[nextLine] - 1
461+
const lineStart = state.bMarks[nextLine] + state.tShift[nextLine]
441462
const lineEnd = state.eMarks[nextLine]
442-
const currentLine = state.src.slice(lineStart, lineEnd)
463+
const currentLine = state.src.slice(lineStart - 1, lineEnd)
443464
if (currentLine.trim() === closeDelim) {
444465
found = true
445466
break
@@ -454,18 +475,13 @@ export function applyMath(md: MarkdownIt, mathOpts?: MathOptions) {
454475
}
455476
}
456477

457-
// For bracket-delimited math, ensure it's math-like before accepting
458-
if (openDelim === '[' && !isMathLike(content))
459-
return false
460-
461478
const token: any = state.push('math_block', 'math', 0)
462-
token.content = normalizeStandaloneBackslashT(content, mathOpts) // 规范化 \t -> \\\t
479+
token.content = normalizeStandaloneBackslashT(content)
463480
token.markup
464481
= openDelim === '$$' ? '$$' : openDelim === '[' ? '[]' : '\\[\\]'
465482
token.map = [startLine, nextLine + 1]
466483
token.block = true
467484
token.loading = !found
468-
469485
state.line = nextLine + 1
470486
return true
471487
}

test/normalize-backslash-direct.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,26 @@ describe('normalizeStandaloneBackslashT direct tests', () => {
77
expect(out).toBe('A\\tB')
88
})
99

10+
it('no-op for already normalized string', () => {
11+
const out = normalizeStandaloneBackslashT('\text{已知三维列向量}\boldsymbol{alpha },\boldsymbol{\beta }\text{,若}\boldsymbol{alpha }^T\boldsymbol{\beta }=0\text{,这个式子和正交补空间有什么联系吗}')
12+
expect(out).toBe('\\text{已知三维列向量}\\boldsymbol{\\alpha },\\boldsymbol{\\beta }\\text{,若}\\boldsymbol{\\alpha }^T\\boldsymbol{\\beta }=0\\text{,这个式子和正交补空间有什么联系吗}')
13+
})
14+
15+
it('no-op for already normalized string-1', () => {
16+
const out = normalizeStandaloneBackslashT('W^\perp = \{ \mathbf{v} \in \mathbb{R}^3 \mid \mathbf{v} \cdot \mathbf{w} = 0 \text{ 对于所有 } \mathbf{w} \in W \}')
17+
expect(out).toBe('W^\\perp = \{ \\mathbf{v} \\in \\mathbb{R}^3 \\mid \\mathbf{v} \\cdot \\mathbf{w} = 0 \\text{ 对于所有 } \\mathbf{w} \\in W \}')
18+
})
19+
20+
it('no-op for already normalized string-2', () => {
21+
const out = normalizeStandaloneBackslashT(`\[
22+
\text{付费转化率} = \left( \frac{\text{付费用户数}}{\text{月活用户数}} \right) \times 100\%
23+
\]`)
24+
console.log({ out })
25+
expect(out).toBe(`\[
26+
\\text{付费转化率} = \\left( \\frac{\\text{付费用户数}}{\\text{月活用户数}} \\right) \\times 100\%
27+
\]`)
28+
})
29+
1030
it('normalizes a raw string to \\t', () => {
1131
const out = normalizeStandaloneBackslashT('atb')
1232

test/normalize-backslash-escaped.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { normalizeStandaloneBackslashT } from '../src/utils/markdown/plugins/mat
44
describe('normalizeStandaloneBackslashT - already escaped', () => {
55
it('does not double-escape already escaped katex commands', () => {
66
expect(normalizeStandaloneBackslashT('\\in')).toBe('\\in')
7-
expect(normalizeStandaloneBackslashT('text \\alpha more')).toBe('text \\alpha more')
7+
expect(normalizeStandaloneBackslashT('text \\alpha more')).toBe('\\text \\alpha more')
88
})
99

1010
it('respects options: custom commands and disabling exclamation escape', () => {

0 commit comments

Comments
 (0)