www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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 });