errors-spec.js (15456B)
1 /* global beforeEach: false */ 2 /* global jasmine: false */ 3 /* global expect: false */ 4 /* global it: false */ 5 /* global describe: false */ 6 7 const parseTree = require("../src/parseTree"); 8 const Settings = require("../src/Settings"); 9 10 const defaultSettings = new Settings({}); 11 12 beforeEach(function() { 13 jasmine.addMatchers({ 14 toFailWithParseError: function(util, customEqualityTesters) { 15 const prefix = "KaTeX parse error: "; 16 return { 17 compare: function(actual, expected) { 18 try { 19 parseTree(actual, defaultSettings); 20 return { 21 pass: false, 22 message: "'" + actual + "' parsed without error", 23 }; 24 } catch (e) { 25 if (expected === undefined) { 26 return { 27 pass: true, 28 message: "'" + actual + "' parsed with error", 29 }; 30 } 31 const msg = e.message; 32 const exp = prefix + expected; 33 if (msg === exp) { 34 return { 35 pass: true, 36 message: "'" + actual + "'" + 37 " parsed with error '" + expected + "'", 38 }; 39 } else if (msg.slice(0, 19) === prefix) { 40 return { 41 pass: false, 42 message: "'" + actual + "'" + 43 " parsed with error '" + msg.slice(19) + 44 "' but expected '" + expected + "'", 45 }; 46 } else { 47 return { 48 pass: false, 49 message: "'" + actual + "'" + 50 " caused error '" + msg + 51 "' but expected '" + exp + "'", 52 }; 53 } 54 } 55 }, 56 }; 57 }, 58 }); 59 }); 60 61 describe("Parser:", function() { 62 63 describe("#handleInfixNodes", function() { 64 it("rejects repeated infix operators", function() { 65 expect("1\\over 2\\over 3").toFailWithParseError( 66 "only one infix operator per group at position 9: " + 67 "1\\over 2\\̲o̲v̲e̲r̲ 3"); 68 }); 69 it("rejects conflicting infix operators", function() { 70 expect("1\\over 2\\choose 3").toFailWithParseError( 71 "only one infix operator per group at position 9: " + 72 "1\\over 2\\̲c̲h̲o̲o̲s̲e̲ 3"); 73 }); 74 }); 75 76 describe("#handleSupSubscript", function() { 77 it("rejects ^ at end of group", function() { 78 expect("{1^}").toFailWithParseError( 79 "Expected group after '^' at position 3: {1^̲}"); 80 }); 81 it("rejects _ at end of input", function() { 82 expect("1_").toFailWithParseError( 83 "Expected group after '_' at position 2: 1_̲"); 84 }); 85 it("rejects \\sqrt as argument to ^", function() { 86 expect("1^\\sqrt{2}").toFailWithParseError( 87 "Got function '\\sqrt' with no arguments as superscript" + 88 " at position 2: 1^̲\\sqrt{2}"); 89 }); 90 }); 91 92 describe("#parseAtom", function() { 93 it("rejects \\limits without operator", function() { 94 expect("\\alpha\\limits\\omega").toFailWithParseError( 95 "Limit controls must follow a math operator" + 96 " at position 7: \\alpha\\̲l̲i̲m̲i̲t̲s̲\\omega"); 97 }); 98 it("rejects \\limits at the beginning of the input", function() { 99 expect("\\limits\\omega").toFailWithParseError( 100 "Limit controls must follow a math operator" + 101 " at position 1: \\̲l̲i̲m̲i̲t̲s̲\\omega"); 102 }); 103 it("rejects double superscripts", function() { 104 expect("1^2^3").toFailWithParseError( 105 "Double superscript at position 4: 1^2^̲3"); 106 expect("1^{2+3}_4^5").toFailWithParseError( 107 "Double superscript at position 10: 1^{2+3}_4^̲5"); 108 }); 109 it("rejects double superscripts involving primes", function() { 110 expect("1'_2^3").toFailWithParseError( 111 "Double superscript at position 5: 1'_2^̲3"); 112 expect("1^2'").toFailWithParseError( 113 "Double superscript at position 4: 1^2'̲"); 114 expect("1^2_3'").toFailWithParseError( 115 "Double superscript at position 6: 1^2_3'̲"); 116 expect("1'_2'").toFailWithParseError( 117 "Double superscript at position 5: 1'_2'̲"); 118 }); 119 it("rejects double subscripts", function() { 120 expect("1_2_3").toFailWithParseError( 121 "Double subscript at position 4: 1_2_̲3"); 122 expect("1_{2+3}^4_5").toFailWithParseError( 123 "Double subscript at position 10: 1_{2+3}^4_̲5"); 124 }); 125 }); 126 127 describe("#parseImplicitGroup", function() { 128 it("reports unknown environments", function() { 129 expect("\\begin{foo}bar\\end{foo}").toFailWithParseError( 130 "No such environment: foo at position 7:" + 131 " \\begin{̲f̲o̲o̲}̲bar\\end{foo}"); 132 }); 133 it("reports mismatched environments", function() { 134 expect("\\begin{pmatrix}1&2\\\\3&4\\end{bmatrix}+5") 135 .toFailWithParseError( 136 "Mismatch: \\begin{pmatrix} matched by \\end{bmatrix}" + 137 " at position 24: …matrix}1&2\\\\3&4\\̲e̲n̲d̲{bmatrix}+5"); 138 }); 139 }); 140 141 describe("#parseFunction", function() { 142 it("rejects math-mode functions in text mode", function() { 143 expect("\\text{\\sqrt2 is irrational}").toFailWithParseError( 144 "Can't use function '\\sqrt' in text mode" + 145 " at position 7: \\text{\\̲s̲q̲r̲t̲2 is irrational…"); 146 }); 147 }); 148 149 describe("#parseArguments", function() { 150 it("complains about missing argument at end of input", function() { 151 expect("2\\sqrt").toFailWithParseError( 152 "Expected group after '\\sqrt' at end of input: 2\\sqrt"); 153 }); 154 it("complains about missing argument at end of group", function() { 155 expect("1^{2\\sqrt}").toFailWithParseError( 156 "Expected group after '\\sqrt'" + 157 " at position 10: 1^{2\\sqrt}̲"); 158 }); 159 it("complains about functions as arguments to others", function() { 160 // TODO: The position looks pretty wrong here 161 expect("\\sqrt\\over2").toFailWithParseError( 162 "Got function '\\over' as argument to '\\sqrt'" + 163 " at position 6: \\sqrt\\̲o̲v̲e̲r̲2"); 164 }); 165 }); 166 167 describe("#parseArguments", function() { 168 it("complains about missing argument at end of input", function() { 169 expect("2\\sqrt").toFailWithParseError( 170 "Expected group after '\\sqrt' at end of input: 2\\sqrt"); 171 }); 172 it("complains about missing argument at end of group", function() { 173 expect("1^{2\\sqrt}").toFailWithParseError( 174 "Expected group after '\\sqrt'" + 175 " at position 10: 1^{2\\sqrt}̲"); 176 }); 177 it("complains about functions as arguments to others", function() { 178 // TODO: The position looks pretty wrong here 179 expect("\\sqrt\\over2").toFailWithParseError( 180 "Got function '\\over' as argument to '\\sqrt'" + 181 " at position 6: \\sqrt\\̲o̲v̲e̲r̲2"); 182 }); 183 }); 184 185 }); 186 187 describe("Parser.expect calls:", function() { 188 189 describe("#parseInput expecting EOF", function() { 190 it("complains about extra }", function() { 191 expect("{1+2}}").toFailWithParseError( 192 "Expected 'EOF', got '}' at position 6: {1+2}}̲"); 193 }); 194 it("complains about extra \\end", function() { 195 expect("x\\end{matrix}").toFailWithParseError( 196 "Expected 'EOF', got '\\end' at position 2:" + 197 " x\\̲e̲n̲d̲{matrix}"); 198 }); 199 it("complains about top-level \\\\", function() { 200 expect("1\\\\2").toFailWithParseError( 201 "Expected 'EOF', got '\\\\' at position 2: 1\\̲\\̲2"); 202 }); 203 it("complains about top-level &", function() { 204 expect("1&2").toFailWithParseError( 205 "Expected 'EOF', got '&' at position 2: 1&̲2"); 206 }); 207 }); 208 209 describe("#parseImplicitGroup expecting \\right", function() { 210 it("rejects missing \\right", function() { 211 expect("\\left(1+2)").toFailWithParseError( 212 "Expected '\\right', got 'EOF' at end of input:" + 213 " \\left(1+2)"); 214 }); 215 it("rejects incorrectly scoped \\right", function() { 216 expect("{\\left(1+2}\\right)").toFailWithParseError( 217 "Expected '\\right', got '}' at position 11:" + 218 " {\\left(1+2}̲\\right)"); 219 }); 220 }); 221 222 // Can't test the expectation for \end after an environment 223 // since all existing arrays use parseArray which has its own expectation. 224 225 describe("#parseSpecialGroup expecting braces", function() { 226 it("complains about missing { for color", function() { 227 expect("\\color#ffffff{text}").toFailWithParseError( 228 "Expected '{', got '#' at position 7:" + 229 " \\color#̲ffffff{text}"); 230 }); 231 it("complains about missing { for size", function() { 232 expect("\\rule{1em}[2em]").toFailWithParseError( 233 "Invalid size: '[' at position 11: \\rule{1em}[̲2em]"); 234 }); 235 // Can't test for the [ of an optional group since it's optional 236 it("complains about missing } for color", function() { 237 expect("\\color{#ffffff{text}").toFailWithParseError( 238 "Invalid color: '#ffffff{text' at position 8:" + 239 " \\color{#̲f̲f̲f̲f̲f̲f̲{̲t̲e̲x̲t̲}"); 240 }); 241 it("complains about missing ] for size", function() { 242 expect("\\rule[1em{2em}{3em}").toFailWithParseError( 243 "Unexpected end of input in size" + 244 " at position 7: \\rule[1̲e̲m̲{̲2̲e̲m̲}̲{̲3̲e̲m̲}̲"); 245 }); 246 it("complains about missing ] for size at end of input", function() { 247 expect("\\rule[1em").toFailWithParseError( 248 "Unexpected end of input in size" + 249 " at position 7: \\rule[1̲e̲m̲"); 250 }); 251 it("complains about missing } for color at end of input", function() { 252 expect("\\color{#123456").toFailWithParseError( 253 "Unexpected end of input in color" + 254 " at position 8: \\color{#̲1̲2̲3̲4̲5̲6̲"); 255 }); 256 }); 257 258 describe("#parseGroup expecting }", function() { 259 it("at end of file", function() { 260 expect("\\sqrt{2").toFailWithParseError( 261 "Expected '}', got 'EOF' at end of input: \\sqrt{2"); 262 }); 263 }); 264 265 describe("#parseOptionalGroup expecting ]", function() { 266 it("at end of file", function() { 267 expect("\\sqrt[3").toFailWithParseError( 268 "Expected ']', got 'EOF' at end of input: \\sqrt[3"); 269 }); 270 it("before group", function() { 271 expect("\\sqrt[3{2}").toFailWithParseError( 272 "Expected ']', got 'EOF' at end of input: \\sqrt[3{2}"); 273 }); 274 }); 275 276 }); 277 278 describe("environments.js:", function() { 279 280 describe("parseArray", function() { 281 it("rejects missing \\end", function() { 282 expect("\\begin{matrix}1").toFailWithParseError( 283 "Expected & or \\\\ or \\end at end of input:" + 284 " \\begin{matrix}1"); 285 }); 286 it("rejects incorrectly scoped \\end", function() { 287 expect("{\\begin{matrix}1}\\end{matrix}").toFailWithParseError( 288 "Expected & or \\\\\ or \\end at position 17:" + 289 " …\\begin{matrix}1}̲\\end{matrix}"); 290 }); 291 }); 292 293 describe("array environment", function() { 294 it("rejects unknown column types", function() { 295 // TODO: The error position here looks strange 296 expect("\\begin{array}{cba}\\end{array}").toFailWithParseError( 297 "Unknown column alignment: b at position 16:" + 298 " \\begin{array}{cb̲a}\\end{array}"); 299 }); 300 }); 301 302 }); 303 304 describe("functions.js:", function() { 305 306 describe("delimiter functions", function() { 307 it("reject invalid opening delimiters", function() { 308 expect("\\bigl 1 + 2 \\bigr").toFailWithParseError( 309 "Invalid delimiter: '1' after '\\bigl' at position 7:" + 310 " \\bigl 1̲ + 2 \\bigr"); 311 }); 312 it("reject invalid closing delimiters", function() { 313 expect("\\bigl(1+2\\bigr=3").toFailWithParseError( 314 "Invalid delimiter: '=' after '\\bigr' at position 15:" + 315 " \\bigl(1+2\\bigr=̲3"); 316 }); 317 }); 318 319 describe("\\begin and \\end", function() { 320 it("reject invalid environment names", function() { 321 expect("\\begin x\\end y").toFailWithParseError( 322 "Invalid environment name at position 8: \\begin x̲\\end y"); 323 }); 324 }); 325 326 }); 327 328 describe("Lexer:", function() { 329 330 describe("#_innerLex", function() { 331 it("rejects lone surrogate char", function() { 332 expect("\udcba").toFailWithParseError( 333 "Unexpected character: '\udcba' at position 1:" + 334 " \udcba\u0332"); 335 }); 336 it("rejects lone backslash at end of input", function() { 337 expect("\\").toFailWithParseError( 338 "Unexpected character: '\\' at position 1: \\̲"); 339 }); 340 }); 341 342 describe("#_innerLexColor", function() { 343 it("reject hex notation without #", function() { 344 expect("\\color{1a2b3c}{foo}").toFailWithParseError( 345 "Invalid color: '1a2b3c'" + 346 " at position 8: \\color{1̲a̲2̲b̲3̲c̲}{foo}"); 347 }); 348 }); 349 350 describe("#_innerLexSize", function() { 351 it("reject size without unit", function() { 352 expect("\\rule{0}{2em}").toFailWithParseError( 353 "Invalid size: '0' at position 7: \\rule{0̲}{2em}"); 354 }); 355 it("reject size with bogus unit", function() { 356 expect("\\rule{1au}{2em}").toFailWithParseError( 357 "Invalid unit: 'au' at position 7: \\rule{1̲a̲u̲}{2em}"); 358 }); 359 it("reject size without number", function() { 360 expect("\\rule{em}{2em}").toFailWithParseError( 361 "Invalid size: 'em' at position 7: \\rule{e̲m̲}{2em}"); 362 }); 363 }); 364 365 });