|
1 | | -import { describe, it, expect } from 'vitest'; |
2 | | -import { maskInlineLaTeX } from './latex-protection'; |
| 1 | +/* eslint-disable no-irregular-whitespace */ |
| 2 | +import { describe, it, expect, test } from 'vitest'; |
| 3 | +import { maskInlineLaTeX, preprocessLaTeX } from './latex-protection'; |
3 | 4 |
|
4 | 5 | describe('maskInlineLaTeX', () => { |
5 | 6 | it('should protect LaTeX $x + y$ but not money $3.99', () => { |
@@ -101,3 +102,122 @@ describe('maskInlineLaTeX', () => { |
101 | 102 | expect(latexExpressions).toEqual([]); |
102 | 103 | }); |
103 | 104 | }); |
| 105 | + |
| 106 | +describe('preprocessLaTeX', () => { |
| 107 | + test('converts inline \\( ... \\) to $...$', () => { |
| 108 | + const input = |
| 109 | + '\\( \\mathrm{GL}_2(\\mathbb{F}_7) \\): Group of invertible matrices with entries in \\(\\mathbb{F}_7\\).'; |
| 110 | + const output = preprocessLaTeX(input); |
| 111 | + expect(output).toBe( |
| 112 | + '$ \\mathrm{GL}_2(\\mathbb{F}_7) $: Group of invertible matrices with entries in $\\mathbb{F}_7$.' |
| 113 | + ); |
| 114 | + }); |
| 115 | + |
| 116 | + test('preserves display math \\[ ... \\] and protects adjacent text', () => { |
| 117 | + const input = `Some kernel of \\(\\mathrm{SL}_2(\\mathbb{F}_7)\\): |
| 118 | + \\[ |
| 119 | + \\left\\{ \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix}, \\begin{pmatrix} -1 & 0 \\\\ 0 & -1 \\end{pmatrix} \\right\\} = \\{\\pm I\\} |
| 120 | + \\]`; |
| 121 | + const output = preprocessLaTeX(input); |
| 122 | + |
| 123 | + expect(output).toBe(`Some kernel of $\\mathrm{SL}_2(\\mathbb{F}_7)$: |
| 124 | + $$ |
| 125 | + \\left\\{ \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix}, \\begin{pmatrix} -1 & 0 \\\\ 0 & -1 \\end{pmatrix} \\right\\} = \\{\\pm I\\} |
| 126 | + $$`); |
| 127 | + }); |
| 128 | + |
| 129 | + test('handles standalone display math equation', () => { |
| 130 | + const input = `Algebra: |
| 131 | +\\[ |
| 132 | +x = \\frac{-b \\pm \\sqrt{\\,b^{2}-4ac\\,}}{2a} |
| 133 | +\\]`; |
| 134 | + const output = preprocessLaTeX(input); |
| 135 | + |
| 136 | + expect(output).toBe(`Algebra: |
| 137 | +$$ |
| 138 | +x = \\frac{-b \\pm \\sqrt{\\,b^{2}-4ac\\,}}{2a} |
| 139 | +$$`); |
| 140 | + }); |
| 141 | + |
| 142 | + test('does not interpret currency values as LaTeX', () => { |
| 143 | + const input = 'I have $10, $3.99 and $x + y$ and $100x$. The amount is $2,000.'; |
| 144 | + const output = preprocessLaTeX(input); |
| 145 | + |
| 146 | + expect(output).toBe('I have \\$10, \\$3.99 and $x + y$ and $100x$. The amount is \\$2,000.'); |
| 147 | + }); |
| 148 | + |
| 149 | + test('ignores dollar signs followed by digits (money), but keeps valid math $x + y$', () => { |
| 150 | + const input = 'I have $10, $3.99 and $x + y$ and $100x$. The amount is $2,000.'; |
| 151 | + const output = preprocessLaTeX(input); |
| 152 | + |
| 153 | + expect(output).toBe('I have \\$10, \\$3.99 and $x + y$ and $100x$. The amount is \\$2,000.'); |
| 154 | + }); |
| 155 | + |
| 156 | + test('handles real-world word problems with amounts and no math delimiters', () => { |
| 157 | + const input = |
| 158 | + 'Emma buys 2 cupcakes for $3 each and 1 cookie for $1.50. How much money does she spend in total?'; |
| 159 | + const output = preprocessLaTeX(input); |
| 160 | + |
| 161 | + expect(output).toBe( |
| 162 | + 'Emma buys 2 cupcakes for \\$3 each and 1 cookie for \\$1.50. How much money does she spend in total?' |
| 163 | + ); |
| 164 | + }); |
| 165 | + |
| 166 | + test('handles decimal amounts in word problem correctly', () => { |
| 167 | + const input = |
| 168 | + 'Maria has $20. She buys a notebook for $4.75 and a pack of pencils for $3.25. How much change does she receive?'; |
| 169 | + const output = preprocessLaTeX(input); |
| 170 | + |
| 171 | + expect(output).toBe( |
| 172 | + 'Maria has \\$20. She buys a notebook for \\$4.75 and a pack of pencils for \\$3.25. How much change does she receive?' |
| 173 | + ); |
| 174 | + }); |
| 175 | + |
| 176 | + test('preserves display math with surrounding non-ASCII text', () => { |
| 177 | + const input = `1 kg の質量は |
| 178 | + \\[ |
| 179 | + E = (1\\ \\text{kg}) \\times (3.0 \\times 10^8\\ \\text{m/s})^2 \\approx 9.0 \\times 10^{16}\\ \\text{J} |
| 180 | + \\] |
| 181 | + というエネルギーに相当します。これは約 21 百万トンの TNT が爆発したときのエネルギーに匹敵します。`; |
| 182 | + const output = preprocessLaTeX(input); |
| 183 | + |
| 184 | + expect(output).toBe( |
| 185 | + `1 kg の質量は |
| 186 | + $$ |
| 187 | + E = (1\\ \\text{kg}) \\times (3.0 \\times 10^8\\ \\text{m/s})^2 \\approx 9.0 \\times 10^{16}\\ \\text{J} |
| 188 | + $$ |
| 189 | + というエネルギーに相当します。これは約 21 百万トンの TNT が爆発したときのエネルギーに匹敵します。` |
| 190 | + ); |
| 191 | + }); |
| 192 | + |
| 193 | + test('converts \\[ ... \\] even when preceded by text without space', () => { |
| 194 | + const input = 'Algebra: \\[x = \\frac{-b \\pm \\sqrt{\\,b^{2}-4ac\\,}}{2a}\\]'; |
| 195 | + const output = preprocessLaTeX(input); |
| 196 | + |
| 197 | + expect(output).toBe('Algebra: $$x = \\frac{-b \\pm \\sqrt{\\,b^{2}-4ac\\,}}{2a}$$'); |
| 198 | + }); |
| 199 | + |
| 200 | + test('escapes isolated $ before digits ($5 → \\$5), but not valid math', () => { |
| 201 | + const input = 'This costs $5 and this is math $x^2$. $100 is money.'; |
| 202 | + const output = preprocessLaTeX(input); |
| 203 | + |
| 204 | + expect(output).toBe('This costs \\$5 and this is math $x^2$. \\$100 is money.'); |
| 205 | + // Note: Since $x^2$ is detected as valid LaTeX, it's preserved. |
| 206 | + // $5 becomes \$5 only *after* real math is masked — but here it's correct because the masking logic avoids treating $5 as math. |
| 207 | + }); |
| 208 | + |
| 209 | + test('handles mhchem notation safely if present', () => { |
| 210 | + const input = 'Chemical reaction: \\( \\ce{H2O} \\) and $\\ce{CO2}$'; |
| 211 | + const output = preprocessLaTeX(input); |
| 212 | + |
| 213 | + expect(output).toBe('Chemical reaction: $ \\ce{H2O} $ and $\\\\ce{CO2}$'); |
| 214 | + // Note: \\ce{...} remains, but $\\ce{...} → $\\\\ce{...} via escapeMhchem |
| 215 | + }); |
| 216 | + |
| 217 | + test('preserves code blocks', () => { |
| 218 | + const input = 'Inline code: `sum $total` and block:\n```\ndollar $amount\n```\nEnd.'; |
| 219 | + const output = preprocessLaTeX(input); |
| 220 | + |
| 221 | + expect(output).toBe(input); // Code blocks prevent misinterpretation |
| 222 | + }); |
| 223 | +}); |
0 commit comments