www

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

katex-spec.js (69779B)


      1 /* eslint max-len:0 */
      2 /* global beforeEach: false */
      3 /* global jasmine: false */
      4 /* global expect: false */
      5 /* global it: false */
      6 /* global describe: false */
      7 
      8 const buildMathML = require("../src/buildMathML");
      9 const buildTree = require("../src/buildTree");
     10 const katex = require("../katex");
     11 const ParseError = require("../src/ParseError");
     12 const parseTree = require("../src/parseTree");
     13 const Options = require("../src/Options");
     14 const Settings = require("../src/Settings");
     15 const Style = require("../src/Style");
     16 
     17 const defaultSettings = new Settings({});
     18 const defaultOptions = new Options({
     19     style: Style.TEXT,
     20     size: "size5",
     21 });
     22 
     23 const _getBuilt = function(expr, settings) {
     24     const usedSettings = settings ? settings : defaultSettings;
     25     const parsedTree = parseTree(expr, usedSettings);
     26     const rootNode = buildTree(parsedTree, expr, usedSettings);
     27 
     28     // grab the root node of the HTML rendering
     29     const builtHTML = rootNode.children[1];
     30 
     31     // Remove the outer .katex and .katex-inner layers
     32     return builtHTML.children[2].children;
     33 };
     34 
     35 /**
     36  * Return the root node of the rendered HTML.
     37  * @param expr
     38  * @param settings
     39  * @returns {Object}
     40  */
     41 const getBuilt = function(expr, settings) {
     42     const usedSettings = settings ? settings : defaultSettings;
     43     expect(expr).toBuild(usedSettings);
     44     return _getBuilt(expr, settings);
     45 };
     46 
     47 /**
     48  * Return the root node of the parse tree.
     49  * @param expr
     50  * @param settings
     51  * @returns {Object}
     52  */
     53 const getParsed = function(expr, settings) {
     54     const usedSettings = settings ? settings : defaultSettings;
     55 
     56     expect(expr).toParse(usedSettings);
     57     return parseTree(expr, usedSettings);
     58 };
     59 
     60 const stripPositions = function(expr) {
     61     if (typeof expr !== "object" || expr === null) {
     62         return expr;
     63     }
     64     if (expr.lexer && typeof expr.start === "number") {
     65         delete expr.lexer;
     66         delete expr.start;
     67         delete expr.end;
     68     }
     69     Object.keys(expr).forEach(function(key) {
     70         stripPositions(expr[key]);
     71     });
     72     return expr;
     73 };
     74 
     75 const parseAndSetResult = function(expr, result, settings) {
     76     try {
     77         return parseTree(expr, settings || defaultSettings);
     78     } catch (e) {
     79         result.pass = false;
     80         if (e instanceof ParseError) {
     81             result.message = "'" + expr + "' failed " +
     82                 "parsing with error: " + e.message;
     83         } else {
     84             result.message = "'" + expr + "' failed " +
     85                 "parsing with unknown error: " + e.message;
     86         }
     87     }
     88 };
     89 
     90 beforeEach(function() {
     91     jasmine.addMatchers({
     92 
     93         toParse: function() {
     94             return {
     95                 compare: function(actual, settings) {
     96                     const usedSettings = settings ? settings : defaultSettings;
     97 
     98                     const result = {
     99                         pass: true,
    100                         message: "'" + actual + "' succeeded parsing",
    101                     };
    102                     parseAndSetResult(actual, result, usedSettings);
    103                     return result;
    104                 },
    105             };
    106         },
    107 
    108         toNotParse: function() {
    109             return {
    110                 compare: function(actual, settings) {
    111                     const usedSettings = settings ? settings : defaultSettings;
    112 
    113                     const result = {
    114                         pass: false,
    115                         message: "Expected '" + actual + "' to fail " +
    116                             "parsing, but it succeeded",
    117                     };
    118 
    119                     try {
    120                         parseTree(actual, usedSettings);
    121                     } catch (e) {
    122                         if (e instanceof ParseError) {
    123                             result.pass = true;
    124                             result.message = "'" + actual + "' correctly " +
    125                                 "didn't parse with error: " + e.message;
    126                         } else {
    127                             result.message = "'" + actual + "' failed " +
    128                                 "parsing with unknown error: " + e.message;
    129                         }
    130                     }
    131 
    132                     return result;
    133                 },
    134             };
    135         },
    136 
    137         toBuild: function() {
    138             return {
    139                 compare: function(actual, settings) {
    140                     const usedSettings = settings ? settings : defaultSettings;
    141 
    142                     const result = {
    143                         pass: true,
    144                         message: "'" + actual + "' succeeded in building",
    145                     };
    146 
    147                     expect(actual).toParse(usedSettings);
    148 
    149                     try {
    150                         _getBuilt(actual, settings);
    151                     } catch (e) {
    152                         result.pass = false;
    153                         if (e instanceof ParseError) {
    154                             result.message = "'" + actual + "' failed to " +
    155                                 "build with error: " + e.message;
    156                         } else {
    157                             result.message = "'" + actual + "' failed " +
    158                                 "building with unknown error: " + e.message;
    159                         }
    160                     }
    161 
    162                     return result;
    163                 },
    164             };
    165         },
    166 
    167         toParseLike: function(util, baton) {
    168             return {
    169                 compare: function(actual, expected) {
    170                     const result = {
    171                         pass: true,
    172                         message: "Parse trees of '" + actual +
    173                             "' and '" + expected + "' are equivalent",
    174                     };
    175 
    176                     const actualTree = parseAndSetResult(actual, result);
    177                     if (!actualTree) {
    178                         return result;
    179                     }
    180                     const expectedTree = parseAndSetResult(expected, result);
    181                     if (!expectedTree) {
    182                         return result;
    183                     }
    184                     stripPositions(actualTree);
    185                     stripPositions(expectedTree);
    186                     if (!util.equals(actualTree, expectedTree, baton)) {
    187                         result.pass = false;
    188                         result.message = "Parse trees of '" + actual +
    189                             "' and '" + expected + "' are not equivalent";
    190                     }
    191                     return result;
    192                 },
    193             };
    194         },
    195 
    196     });
    197 });
    198 
    199 describe("A parser", function() {
    200     it("should not fail on an empty string", function() {
    201         expect("").toParse();
    202     });
    203 
    204     it("should ignore whitespace", function() {
    205         const parseA = stripPositions(getParsed("    x    y    "));
    206         const parseB = stripPositions(getParsed("xy"));
    207         expect(parseA).toEqual(parseB);
    208     });
    209 });
    210 
    211 describe("An ord parser", function() {
    212     const expression = "1234|/@.\"`abcdefgzABCDEFGZ";
    213 
    214     it("should not fail", function() {
    215         expect(expression).toParse();
    216     });
    217 
    218     it("should build a list of ords", function() {
    219         const parse = getParsed(expression);
    220 
    221         expect(parse).toBeTruthy();
    222 
    223         for (let i = 0; i < parse.length; i++) {
    224             const group = parse[i];
    225             expect(group.type).toMatch("ord");
    226         }
    227     });
    228 
    229     it("should parse the right number of ords", function() {
    230         const parse = getParsed(expression);
    231 
    232         expect(parse.length).toBe(expression.length);
    233     });
    234 });
    235 
    236 describe("A bin parser", function() {
    237     const expression = "+-*\\cdot\\pm\\div";
    238 
    239     it("should not fail", function() {
    240         expect(expression).toParse();
    241     });
    242 
    243     it("should build a list of bins", function() {
    244         const parse = getParsed(expression);
    245         expect(parse).toBeTruthy();
    246 
    247         for (let i = 0; i < parse.length; i++) {
    248             const group = parse[i];
    249             expect(group.type).toEqual("bin");
    250         }
    251     });
    252 });
    253 
    254 describe("A rel parser", function() {
    255     const expression = "=<>\\leq\\geq\\neq\\nleq\\ngeq\\cong";
    256 
    257     it("should not fail", function() {
    258         expect(expression).toParse();
    259     });
    260 
    261     it("should build a list of rels", function() {
    262         const parse = getParsed(expression);
    263         expect(parse).toBeTruthy();
    264 
    265         for (let i = 0; i < parse.length; i++) {
    266             const group = parse[i];
    267             expect(group.type).toEqual("rel");
    268         }
    269     });
    270 });
    271 
    272 describe("A punct parser", function() {
    273     const expression = ",;\\colon";
    274 
    275     it("should not fail", function() {
    276         expect(expression).toParse();
    277     });
    278 
    279     it("should build a list of puncts", function() {
    280         const parse = getParsed(expression);
    281         expect(parse).toBeTruthy();
    282 
    283         for (let i = 0; i < parse.length; i++) {
    284             const group = parse[i];
    285             expect(group.type).toEqual("punct");
    286         }
    287     });
    288 });
    289 
    290 describe("An open parser", function() {
    291     const expression = "([";
    292 
    293     it("should not fail", function() {
    294         expect(expression).toParse();
    295     });
    296 
    297     it("should build a list of opens", function() {
    298         const parse = getParsed(expression);
    299         expect(parse).toBeTruthy();
    300 
    301         for (let i = 0; i < parse.length; i++) {
    302             const group = parse[i];
    303             expect(group.type).toEqual("open");
    304         }
    305     });
    306 });
    307 
    308 describe("A close parser", function() {
    309     const expression = ")]?!";
    310 
    311     it("should not fail", function() {
    312         expect(expression).toParse();
    313     });
    314 
    315     it("should build a list of closes", function() {
    316         const parse = getParsed(expression);
    317         expect(parse).toBeTruthy();
    318 
    319         for (let i = 0; i < parse.length; i++) {
    320             const group = parse[i];
    321             expect(group.type).toEqual("close");
    322         }
    323     });
    324 });
    325 
    326 describe("A \\KaTeX parser", function() {
    327     it("should not fail", function() {
    328         expect("\\KaTeX").toParse();
    329     });
    330 });
    331 
    332 describe("A subscript and superscript parser", function() {
    333     it("should not fail on superscripts", function() {
    334         expect("x^2").toParse();
    335     });
    336 
    337     it("should not fail on subscripts", function() {
    338         expect("x_3").toParse();
    339     });
    340 
    341     it("should not fail on both subscripts and superscripts", function() {
    342         expect("x^2_3").toParse();
    343 
    344         expect("x_2^3").toParse();
    345     });
    346 
    347     it("should not fail when there is no nucleus", function() {
    348         expect("^3").toParse();
    349         expect("_2").toParse();
    350         expect("^3_2").toParse();
    351         expect("_2^3").toParse();
    352     });
    353 
    354     it("should produce supsubs for superscript", function() {
    355         const parse = getParsed("x^2")[0];
    356 
    357         expect(parse.type).toBe("supsub");
    358         expect(parse.value.base).toBeDefined();
    359         expect(parse.value.sup).toBeDefined();
    360         expect(parse.value.sub).toBeUndefined();
    361     });
    362 
    363     it("should produce supsubs for subscript", function() {
    364         const parse = getParsed("x_3")[0];
    365 
    366         expect(parse.type).toBe("supsub");
    367         expect(parse.value.base).toBeDefined();
    368         expect(parse.value.sub).toBeDefined();
    369         expect(parse.value.sup).toBeUndefined();
    370     });
    371 
    372     it("should produce supsubs for ^_", function() {
    373         const parse = getParsed("x^2_3")[0];
    374 
    375         expect(parse.type).toBe("supsub");
    376         expect(parse.value.base).toBeDefined();
    377         expect(parse.value.sup).toBeDefined();
    378         expect(parse.value.sub).toBeDefined();
    379     });
    380 
    381     it("should produce supsubs for _^", function() {
    382         const parse = getParsed("x_3^2")[0];
    383 
    384         expect(parse.type).toBe("supsub");
    385         expect(parse.value.base).toBeDefined();
    386         expect(parse.value.sup).toBeDefined();
    387         expect(parse.value.sub).toBeDefined();
    388     });
    389 
    390     it("should produce the same thing regardless of order", function() {
    391         const parseA = stripPositions(getParsed("x^2_3"));
    392         const parseB = stripPositions(getParsed("x_3^2"));
    393 
    394         expect(parseA).toEqual(parseB);
    395     });
    396 
    397     it("should not parse double subscripts or superscripts", function() {
    398         expect("x^x^x").toNotParse();
    399 
    400         expect("x_x_x").toNotParse();
    401 
    402         expect("x_x^x_x").toNotParse();
    403 
    404         expect("x_x^x^x").toNotParse();
    405 
    406         expect("x^x_x_x").toNotParse();
    407 
    408         expect("x^x_x^x").toNotParse();
    409     });
    410 
    411     it("should work correctly with {}s", function() {
    412         expect("x^{2+3}").toParse();
    413 
    414         expect("x_{3-2}").toParse();
    415 
    416         expect("x^{2+3}_3").toParse();
    417 
    418         expect("x^2_{3-2}").toParse();
    419 
    420         expect("x^{2+3}_{3-2}").toParse();
    421 
    422         expect("x_{3-2}^{2+3}").toParse();
    423 
    424         expect("x_3^{2+3}").toParse();
    425 
    426         expect("x_{3-2}^2").toParse();
    427     });
    428 
    429     it("should work with nested super/subscripts", function() {
    430         expect("x^{x^x}").toParse();
    431         expect("x^{x_x}").toParse();
    432         expect("x_{x^x}").toParse();
    433         expect("x_{x_x}").toParse();
    434     });
    435 });
    436 
    437 describe("A subscript and superscript tree-builder", function() {
    438     it("should not fail when there is no nucleus", function() {
    439         expect("^3").toBuild();
    440         expect("_2").toBuild();
    441         expect("^3_2").toBuild();
    442         expect("_2^3").toBuild();
    443     });
    444 });
    445 
    446 describe("A parser with limit controls", function() {
    447     it("should fail when the limit control is not preceded by an op node", function() {
    448         expect("3\\nolimits_2^2").toNotParse();
    449         expect("\\sqrt\\limits_2^2").toNotParse();
    450         expect("45 +\\nolimits 45").toNotParse();
    451     });
    452 
    453     it("should parse when the limit control directly follows an op node", function() {
    454         expect("\\int\\limits_2^2 3").toParse();
    455         expect("\\sum\\nolimits_3^4 4").toParse();
    456     });
    457 
    458     it("should parse when the limit control is in the sup/sub area of an op node", function() {
    459         expect("\\int_2^2\\limits").toParse();
    460         expect("\\int^2\\nolimits_2").toParse();
    461         expect("\\int_2\\limits^2").toParse();
    462     });
    463 
    464     it("should allow multiple limit controls in the sup/sub area of an op node", function() {
    465         expect("\\int_2\\nolimits^2\\limits 3").toParse();
    466         expect("\\int\\nolimits\\limits_2^2").toParse();
    467         expect("\\int\\limits\\limits\\limits_2^2").toParse();
    468     });
    469 
    470     it("should have the rightmost limit control determine the limits property " +
    471         "of the preceding op node", function() {
    472 
    473         let parsedInput = getParsed("\\int\\nolimits\\limits_2^2");
    474         expect(parsedInput[0].value.base.value.limits).toBe(true);
    475 
    476         parsedInput = getParsed("\\int\\limits_2\\nolimits^2");
    477         expect(parsedInput[0].value.base.value.limits).toBe(false);
    478     });
    479 });
    480 
    481 describe("A group parser", function() {
    482     it("should not fail", function() {
    483         expect("{xy}").toParse();
    484     });
    485 
    486     it("should produce a single ord", function() {
    487         const parse = getParsed("{xy}");
    488 
    489         expect(parse.length).toBe(1);
    490 
    491         const ord = parse[0];
    492 
    493         expect(ord.type).toMatch("ord");
    494         expect(ord.value).toBeTruthy();
    495     });
    496 });
    497 
    498 describe("An implicit group parser", function() {
    499     it("should not fail", function() {
    500         expect("\\Large x").toParse();
    501         expect("abc {abc \\Large xyz} abc").toParse();
    502     });
    503 
    504     it("should produce a single object", function() {
    505         const parse = getParsed("\\Large abc");
    506 
    507         expect(parse.length).toBe(1);
    508 
    509         const sizing = parse[0];
    510 
    511         expect(sizing.type).toEqual("sizing");
    512         expect(sizing.value).toBeTruthy();
    513     });
    514 
    515     it("should apply only after the function", function() {
    516         const parse = getParsed("a \\Large abc");
    517 
    518         expect(parse.length).toBe(2);
    519 
    520         const sizing = parse[1];
    521 
    522         expect(sizing.type).toEqual("sizing");
    523         expect(sizing.value.value.length).toBe(3);
    524     });
    525 
    526     it("should stop at the ends of groups", function() {
    527         const parse = getParsed("a { b \\Large c } d");
    528 
    529         const group = parse[1];
    530         const sizing = group.value[1];
    531 
    532         expect(sizing.type).toEqual("sizing");
    533         expect(sizing.value.value.length).toBe(1);
    534     });
    535 });
    536 
    537 describe("A function parser", function() {
    538     it("should parse no argument functions", function() {
    539         expect("\\div").toParse();
    540     });
    541 
    542     it("should parse 1 argument functions", function() {
    543         expect("\\blue x").toParse();
    544     });
    545 
    546     it("should parse 2 argument functions", function() {
    547         expect("\\frac 1 2").toParse();
    548     });
    549 
    550     it("should not parse 1 argument functions with no arguments", function() {
    551         expect("\\blue").toNotParse();
    552     });
    553 
    554     it("should not parse 2 argument functions with 0 or 1 arguments", function() {
    555         expect("\\frac").toNotParse();
    556 
    557         expect("\\frac 1").toNotParse();
    558     });
    559 
    560     it("should not parse a function with text right after it", function() {
    561         expect("\\redx").toNotParse();
    562     });
    563 
    564     it("should parse a function with a number right after it", function() {
    565         expect("\\frac12").toParse();
    566     });
    567 
    568     it("should parse some functions with text right after it", function() {
    569         expect("\\;x").toParse();
    570     });
    571 });
    572 
    573 describe("A frac parser", function() {
    574     const expression = "\\frac{x}{y}";
    575     const dfracExpression = "\\dfrac{x}{y}";
    576     const tfracExpression = "\\tfrac{x}{y}";
    577 
    578     it("should not fail", function() {
    579         expect(expression).toParse();
    580     });
    581 
    582     it("should produce a frac", function() {
    583         const parse = getParsed(expression)[0];
    584 
    585         expect(parse.type).toEqual("genfrac");
    586         expect(parse.value.numer).toBeDefined();
    587         expect(parse.value.denom).toBeDefined();
    588     });
    589 
    590     it("should also parse dfrac and tfrac", function() {
    591         expect(dfracExpression).toParse();
    592 
    593         expect(tfracExpression).toParse();
    594     });
    595 
    596     it("should parse dfrac and tfrac as fracs", function() {
    597         const dfracParse = getParsed(dfracExpression)[0];
    598 
    599         expect(dfracParse.type).toEqual("genfrac");
    600         expect(dfracParse.value.numer).toBeDefined();
    601         expect(dfracParse.value.denom).toBeDefined();
    602 
    603         const tfracParse = getParsed(tfracExpression)[0];
    604 
    605         expect(tfracParse.type).toEqual("genfrac");
    606         expect(tfracParse.value.numer).toBeDefined();
    607         expect(tfracParse.value.denom).toBeDefined();
    608     });
    609 
    610     it("should parse atop", function() {
    611         const parse = getParsed("x \\atop y")[0];
    612 
    613         expect(parse.type).toEqual("genfrac");
    614         expect(parse.value.numer).toBeDefined();
    615         expect(parse.value.denom).toBeDefined();
    616         expect(parse.value.hasBarLine).toEqual(false);
    617     });
    618 });
    619 
    620 describe("An over parser", function() {
    621     const simpleOver = "1 \\over x";
    622     const complexOver = "1+2i \\over 3+4i";
    623 
    624     it("should not fail", function() {
    625         expect(simpleOver).toParse();
    626         expect(complexOver).toParse();
    627     });
    628 
    629     it("should produce a frac", function() {
    630         let parse;
    631 
    632         parse = getParsed(simpleOver)[0];
    633 
    634         expect(parse.type).toEqual("genfrac");
    635         expect(parse.value.numer).toBeDefined();
    636         expect(parse.value.denom).toBeDefined();
    637 
    638         parse = getParsed(complexOver)[0];
    639 
    640         expect(parse.type).toEqual("genfrac");
    641         expect(parse.value.numer).toBeDefined();
    642         expect(parse.value.denom).toBeDefined();
    643     });
    644 
    645     it("should create a numerator from the atoms before \\over", function() {
    646         const parse = getParsed(complexOver)[0];
    647 
    648         const numer = parse.value.numer;
    649         expect(numer.value.length).toEqual(4);
    650     });
    651 
    652     it("should create a demonimator from the atoms after \\over", function() {
    653         const parse = getParsed(complexOver)[0];
    654 
    655         const denom = parse.value.numer;
    656         expect(denom.value.length).toEqual(4);
    657     });
    658 
    659     it("should handle empty numerators", function() {
    660         const emptyNumerator = "\\over x";
    661         const parse = getParsed(emptyNumerator)[0];
    662         expect(parse.type).toEqual("genfrac");
    663         expect(parse.value.numer).toBeDefined();
    664         expect(parse.value.denom).toBeDefined();
    665     });
    666 
    667     it("should handle empty denominators", function() {
    668         const emptyDenominator = "1 \\over";
    669         const parse = getParsed(emptyDenominator)[0];
    670         expect(parse.type).toEqual("genfrac");
    671         expect(parse.value.numer).toBeDefined();
    672         expect(parse.value.denom).toBeDefined();
    673     });
    674 
    675     it("should handle \\displaystyle correctly", function() {
    676         const displaystyleExpression = "\\displaystyle 1 \\over 2";
    677         const parse = getParsed(displaystyleExpression)[0];
    678         expect(parse.type).toEqual("genfrac");
    679         expect(parse.value.numer.value[0].type).toEqual("styling");
    680         expect(parse.value.denom).toBeDefined();
    681     });
    682 
    683     it("should handle \\textstyle correctly", function() {
    684         expect("\\textstyle 1 \\over 2")
    685             .toParseLike("\\frac{\\textstyle 1}{2}");
    686         expect("{\\textstyle 1} \\over 2")
    687             .toParseLike("\\frac{\\textstyle 1}{2}");
    688     });
    689 
    690     it("should handle nested factions", function() {
    691         const nestedOverExpression = "{1 \\over 2} \\over 3";
    692         const parse = getParsed(nestedOverExpression)[0];
    693         expect(parse.type).toEqual("genfrac");
    694         expect(parse.value.numer.value[0].type).toEqual("genfrac");
    695         expect(parse.value.numer.value[0].value.numer.value[0].value).toEqual("1");
    696         expect(parse.value.numer.value[0].value.denom.value[0].value).toEqual("2");
    697         expect(parse.value.denom).toBeDefined();
    698         expect(parse.value.denom.value[0].value).toEqual("3");
    699     });
    700 
    701     it("should fail with multiple overs in the same group", function() {
    702         const badMultipleOvers = "1 \\over 2 + 3 \\over 4";
    703         expect(badMultipleOvers).toNotParse();
    704 
    705         const badOverChoose = "1 \\over 2 \\choose 3";
    706         expect(badOverChoose).toNotParse();
    707     });
    708 });
    709 
    710 describe("A sizing parser", function() {
    711     const sizeExpression = "\\Huge{x}\\small{x}";
    712 
    713     it("should not fail", function() {
    714         expect(sizeExpression).toParse();
    715     });
    716 
    717     it("should produce a sizing node", function() {
    718         const parse = getParsed(sizeExpression)[0];
    719 
    720         expect(parse.type).toEqual("sizing");
    721         expect(parse.value).toBeDefined();
    722     });
    723 });
    724 
    725 describe("A text parser", function() {
    726     const textExpression = "\\text{a b}";
    727     const noBraceTextExpression = "\\text x";
    728     const nestedTextExpression =
    729         "\\text{a {b} \\blue{c} \\color{#fff}{x} \\llap{x}}";
    730     const spaceTextExpression = "\\text{  a \\ }";
    731     const leadingSpaceTextExpression = "\\text {moo}";
    732     const badTextExpression = "\\text{a b%}";
    733     const badFunctionExpression = "\\text{\\sqrt{x}}";
    734     const mathTokenAfterText = "\\text{sin}^2";
    735 
    736     it("should not fail", function() {
    737         expect(textExpression).toParse();
    738     });
    739 
    740     it("should produce a text", function() {
    741         const parse = getParsed(textExpression)[0];
    742 
    743         expect(parse.type).toEqual("text");
    744         expect(parse.value).toBeDefined();
    745     });
    746 
    747     it("should produce textords instead of mathords", function() {
    748         const parse = getParsed(textExpression)[0];
    749         const group = parse.value.body;
    750 
    751         expect(group[0].type).toEqual("textord");
    752     });
    753 
    754     it("should not parse bad text", function() {
    755         expect(badTextExpression).toNotParse();
    756     });
    757 
    758     it("should not parse bad functions inside text", function() {
    759         expect(badFunctionExpression).toNotParse();
    760     });
    761 
    762     it("should parse text with no braces around it", function() {
    763         expect(noBraceTextExpression).toParse();
    764     });
    765 
    766     it("should parse nested expressions", function() {
    767         expect(nestedTextExpression).toParse();
    768     });
    769 
    770     it("should contract spaces", function() {
    771         const parse = getParsed(spaceTextExpression)[0];
    772         const group = parse.value.body;
    773 
    774         expect(group[0].type).toEqual("spacing");
    775         expect(group[1].type).toEqual("textord");
    776         expect(group[2].type).toEqual("spacing");
    777         expect(group[3].type).toEqual("spacing");
    778     });
    779 
    780     it("should accept math mode tokens after its argument", function() {
    781         expect(mathTokenAfterText).toParse();
    782     });
    783 
    784     it("should ignore a space before the text group", function() {
    785         const parse = getParsed(leadingSpaceTextExpression)[0];
    786         // [m, o, o]
    787         expect(parse.value.body.length).toBe(3);
    788         expect(
    789             parse.value.body.map(function(n) { return n.value; }).join("")
    790         ).toBe("moo");
    791     });
    792 });
    793 
    794 describe("A color parser", function() {
    795     const colorExpression = "\\blue{x}";
    796     const newColorExpression = "\\redA{x}";
    797     const customColorExpression = "\\color{#fA6}{x}";
    798     const badCustomColorExpression = "\\color{bad-color}{x}";
    799 
    800     it("should not fail", function() {
    801         expect(colorExpression).toParse();
    802     });
    803 
    804     it("should build a color node", function() {
    805         const parse = getParsed(colorExpression)[0];
    806 
    807         expect(parse.type).toEqual("color");
    808         expect(parse.value.color).toBeDefined();
    809         expect(parse.value.value).toBeDefined();
    810     });
    811 
    812     it("should parse a custom color", function() {
    813         expect(customColorExpression).toParse();
    814     });
    815 
    816     it("should correctly extract the custom color", function() {
    817         const parse = getParsed(customColorExpression)[0];
    818 
    819         expect(parse.value.color).toEqual("#fA6");
    820     });
    821 
    822     it("should not parse a bad custom color", function() {
    823         expect(badCustomColorExpression).toNotParse();
    824     });
    825 
    826     it("should parse new colors from the branding guide", function() {
    827         expect(newColorExpression).toParse();
    828     });
    829 
    830     it("should have correct greediness", function() {
    831         expect("\\color{red}a").toParse();
    832         expect("\\color{red}{\\text{a}}").toParse();
    833         expect("\\color{red}\\text{a}").toNotParse();
    834         expect("\\color{red}\\frac12").toNotParse();
    835     });
    836 });
    837 
    838 describe("A tie parser", function() {
    839     const mathTie = "a~b";
    840     const textTie = "\\text{a~ b}";
    841 
    842     it("should parse ties in math mode", function() {
    843         expect(mathTie).toParse();
    844     });
    845 
    846     it("should parse ties in text mode", function() {
    847         expect(textTie).toParse();
    848     });
    849 
    850     it("should produce spacing in math mode", function() {
    851         const parse = getParsed(mathTie);
    852 
    853         expect(parse[1].type).toEqual("spacing");
    854     });
    855 
    856     it("should produce spacing in text mode", function() {
    857         const text = getParsed(textTie)[0];
    858         const parse = text.value.body;
    859 
    860         expect(parse[1].type).toEqual("spacing");
    861     });
    862 
    863     it("should not contract with spaces in text mode", function() {
    864         const text = getParsed(textTie)[0];
    865         const parse = text.value.body;
    866 
    867         expect(parse[2].type).toEqual("spacing");
    868     });
    869 });
    870 
    871 describe("A delimiter sizing parser", function() {
    872     const normalDelim = "\\bigl |";
    873     const notDelim = "\\bigl x";
    874     const bigDelim = "\\Biggr \\langle";
    875 
    876     it("should parse normal delimiters", function() {
    877         expect(normalDelim).toParse();
    878         expect(bigDelim).toParse();
    879     });
    880 
    881     it("should not parse not-delimiters", function() {
    882         expect(notDelim).toNotParse();
    883     });
    884 
    885     it("should produce a delimsizing", function() {
    886         const parse = getParsed(normalDelim)[0];
    887 
    888         expect(parse.type).toEqual("delimsizing");
    889     });
    890 
    891     it("should produce the correct direction delimiter", function() {
    892         const leftParse = getParsed(normalDelim)[0];
    893         const rightParse = getParsed(bigDelim)[0];
    894 
    895         expect(leftParse.value.mclass).toEqual("mopen");
    896         expect(rightParse.value.mclass).toEqual("mclose");
    897     });
    898 
    899     it("should parse the correct size delimiter", function() {
    900         const smallParse = getParsed(normalDelim)[0];
    901         const bigParse = getParsed(bigDelim)[0];
    902 
    903         expect(smallParse.value.size).toEqual(1);
    904         expect(bigParse.value.size).toEqual(4);
    905     });
    906 });
    907 
    908 describe("An overline parser", function() {
    909     const overline = "\\overline{x}";
    910 
    911     it("should not fail", function() {
    912         expect(overline).toParse();
    913     });
    914 
    915     it("should produce an overline", function() {
    916         const parse = getParsed(overline)[0];
    917 
    918         expect(parse.type).toEqual("overline");
    919     });
    920 });
    921 
    922 describe("A rule parser", function() {
    923     const emRule = "\\rule{1em}{2em}";
    924     const exRule = "\\rule{1ex}{2em}";
    925     const badUnitRule = "\\rule{1px}{2em}";
    926     const noNumberRule = "\\rule{1em}{em}";
    927     const incompleteRule = "\\rule{1em}";
    928     const hardNumberRule = "\\rule{   01.24ex}{2.450   em   }";
    929 
    930     it("should not fail", function() {
    931         expect(emRule).toParse();
    932         expect(exRule).toParse();
    933     });
    934 
    935     it("should not parse invalid units", function() {
    936         expect(badUnitRule).toNotParse();
    937 
    938         expect(noNumberRule).toNotParse();
    939     });
    940 
    941     it("should not parse incomplete rules", function() {
    942         expect(incompleteRule).toNotParse();
    943     });
    944 
    945     it("should produce a rule", function() {
    946         const parse = getParsed(emRule)[0];
    947 
    948         expect(parse.type).toEqual("rule");
    949     });
    950 
    951     it("should list the correct units", function() {
    952         const emParse = getParsed(emRule)[0];
    953         const exParse = getParsed(exRule)[0];
    954 
    955         expect(emParse.value.width.unit).toEqual("em");
    956         expect(emParse.value.height.unit).toEqual("em");
    957 
    958         expect(exParse.value.width.unit).toEqual("ex");
    959         expect(exParse.value.height.unit).toEqual("em");
    960     });
    961 
    962     it("should parse the number correctly", function() {
    963         const hardNumberParse = getParsed(hardNumberRule)[0];
    964 
    965         expect(hardNumberParse.value.width.number).toBeCloseTo(1.24);
    966         expect(hardNumberParse.value.height.number).toBeCloseTo(2.45);
    967     });
    968 
    969     it("should parse negative sizes", function() {
    970         const parse = getParsed("\\rule{-1em}{- 0.2em}")[0];
    971 
    972         expect(parse.value.width.number).toBeCloseTo(-1);
    973         expect(parse.value.height.number).toBeCloseTo(-0.2);
    974     });
    975 });
    976 
    977 describe("A kern parser", function() {
    978     const emKern = "\\kern{1em}";
    979     const exKern = "\\kern{1ex}";
    980     const muKern = "\\kern{1mu}";
    981     const badUnitRule = "\\kern{1px}";
    982     const noNumberRule = "\\kern{em}";
    983 
    984     it("should list the correct units", function() {
    985         const emParse = getParsed(emKern)[0];
    986         const exParse = getParsed(exKern)[0];
    987         const muParse = getParsed(muKern)[0];
    988 
    989         expect(emParse.value.dimension.unit).toEqual("em");
    990         expect(exParse.value.dimension.unit).toEqual("ex");
    991         expect(muParse.value.dimension.unit).toEqual("mu");
    992     });
    993 
    994     it("should not parse invalid units", function() {
    995         expect(badUnitRule).toNotParse();
    996         expect(noNumberRule).toNotParse();
    997     });
    998 
    999     it("should parse negative sizes", function() {
   1000         const parse = getParsed("\\kern{-1em}")[0];
   1001         expect(parse.value.dimension.number).toBeCloseTo(-1);
   1002     });
   1003 
   1004     it("should parse positive sizes", function() {
   1005         const parse = getParsed("\\kern{+1em}")[0];
   1006         expect(parse.value.dimension.number).toBeCloseTo(1);
   1007     });
   1008 });
   1009 
   1010 describe("A non-braced kern parser", function() {
   1011     const emKern = "\\kern1em";
   1012     const exKern = "\\kern 1 ex";
   1013     const muKern = "\\kern 1mu";
   1014     const badUnitRule = "\\kern1px";
   1015     const noNumberRule = "\\kern em";
   1016 
   1017     it("should list the correct units", function() {
   1018         const emParse = getParsed(emKern)[0];
   1019         const exParse = getParsed(exKern)[0];
   1020         const muParse = getParsed(muKern)[0];
   1021 
   1022         expect(emParse.value.dimension.unit).toEqual("em");
   1023         expect(exParse.value.dimension.unit).toEqual("ex");
   1024         expect(muParse.value.dimension.unit).toEqual("mu");
   1025     });
   1026 
   1027     it("should not parse invalid units", function() {
   1028         expect(badUnitRule).toNotParse();
   1029         expect(noNumberRule).toNotParse();
   1030     });
   1031 
   1032     it("should parse negative sizes", function() {
   1033         const parse = getParsed("\\kern-1em")[0];
   1034         expect(parse.value.dimension.number).toBeCloseTo(-1);
   1035     });
   1036 
   1037     it("should parse positive sizes", function() {
   1038         const parse = getParsed("\\kern+1em")[0];
   1039         expect(parse.value.dimension.number).toBeCloseTo(1);
   1040     });
   1041 });
   1042 
   1043 describe("A left/right parser", function() {
   1044     const normalLeftRight = "\\left( \\dfrac{x}{y} \\right)";
   1045     const emptyRight = "\\left( \\dfrac{x}{y} \\right.";
   1046 
   1047     it("should not fail", function() {
   1048         expect(normalLeftRight).toParse();
   1049     });
   1050 
   1051     it("should produce a leftright", function() {
   1052         const parse = getParsed(normalLeftRight)[0];
   1053 
   1054         expect(parse.type).toEqual("leftright");
   1055         expect(parse.value.left).toEqual("(");
   1056         expect(parse.value.right).toEqual(")");
   1057     });
   1058 
   1059     it("should error when it is mismatched", function() {
   1060         const unmatchedLeft = "\\left( \\dfrac{x}{y}";
   1061         const unmatchedRight = "\\dfrac{x}{y} \\right)";
   1062 
   1063         expect(unmatchedLeft).toNotParse();
   1064 
   1065         expect(unmatchedRight).toNotParse();
   1066     });
   1067 
   1068     it("should error when braces are mismatched", function() {
   1069         const unmatched = "{ \\left( \\dfrac{x}{y} } \\right)";
   1070         expect(unmatched).toNotParse();
   1071     });
   1072 
   1073     it("should error when non-delimiters are provided", function() {
   1074         const nonDelimiter = "\\left$ \\dfrac{x}{y} \\right)";
   1075         expect(nonDelimiter).toNotParse();
   1076     });
   1077 
   1078     it("should parse the empty '.' delimiter", function() {
   1079         expect(emptyRight).toParse();
   1080     });
   1081 
   1082     it("should parse the '.' delimiter with normal sizes", function() {
   1083         const normalEmpty = "\\Bigl .";
   1084         expect(normalEmpty).toParse();
   1085     });
   1086 
   1087     it("should handle \\middle", function() {
   1088         const normalMiddle = "\\left( \\dfrac{x}{y} \\middle| \\dfrac{y}{z} \\right)";
   1089         expect(normalMiddle).toParse();
   1090     });
   1091 
   1092     it("should handle multiple \\middles", function() {
   1093         const multiMiddle = "\\left( \\dfrac{x}{y} \\middle| \\dfrac{y}{z} \\middle/ \\dfrac{z}{q} \\right)";
   1094         expect(multiMiddle).toParse();
   1095     });
   1096 
   1097     it("should handle nested \\middles", function() {
   1098         const nestedMiddle = "\\left( a^2 \\middle| \\left( b \\middle/ c \\right) \\right)";
   1099         expect(nestedMiddle).toParse();
   1100     });
   1101 
   1102     it("should error when \\middle is not in \\left...\\right", function() {
   1103         const unmatchedMiddle = "(\\middle|\\dfrac{x}{y})";
   1104         expect(unmatchedMiddle).toNotParse();
   1105     });
   1106 });
   1107 
   1108 describe("A begin/end parser", function() {
   1109 
   1110     it("should parse a simple environment", function() {
   1111         expect("\\begin{matrix}a&b\\\\c&d\\end{matrix}").toParse();
   1112     });
   1113 
   1114     it("should parse an environment with argument", function() {
   1115         expect("\\begin{array}{cc}a&b\\\\c&d\\end{array}").toParse();
   1116     });
   1117 
   1118     it("should error when name is mismatched", function() {
   1119         expect("\\begin{matrix}a&b\\\\c&d\\end{pmatrix}").toNotParse();
   1120     });
   1121 
   1122     it("should error when commands are mismatched", function() {
   1123         expect("\\begin{matrix}a&b\\\\c&d\\right{pmatrix}").toNotParse();
   1124     });
   1125 
   1126     it("should error when end is missing", function() {
   1127         expect("\\begin{matrix}a&b\\\\c&d").toNotParse();
   1128     });
   1129 
   1130     it("should error when braces are mismatched", function() {
   1131         expect("{\\begin{matrix}a&b\\\\c&d}\\end{matrix}").toNotParse();
   1132     });
   1133 
   1134     it("should cooperate with infix notation", function() {
   1135         expect("\\begin{matrix}0&1\\over2&3\\\\4&5&6\\end{matrix}").toParse();
   1136     });
   1137 
   1138     it("should nest", function() {
   1139         const m1 = "\\begin{pmatrix}1&2\\\\3&4\\end{pmatrix}";
   1140         const m2 = "\\begin{array}{rl}" + m1 + "&0\\\\0&" + m1 + "\\end{array}";
   1141         expect(m2).toParse();
   1142     });
   1143 
   1144     it("should allow \\cr as a line terminator", function() {
   1145         expect("\\begin{matrix}a&b\\cr c&d\\end{matrix}").toParse();
   1146     });
   1147 });
   1148 
   1149 describe("A sqrt parser", function() {
   1150     const sqrt = "\\sqrt{x}";
   1151     const missingGroup = "\\sqrt";
   1152 
   1153     it("should parse square roots", function() {
   1154         expect(sqrt).toParse();
   1155     });
   1156 
   1157     it("should error when there is no group", function() {
   1158         expect(missingGroup).toNotParse();
   1159     });
   1160 
   1161     it("should produce sqrts", function() {
   1162         const parse = getParsed(sqrt)[0];
   1163 
   1164         expect(parse.type).toEqual("sqrt");
   1165     });
   1166 });
   1167 
   1168 describe("A TeX-compliant parser", function() {
   1169     it("should work", function() {
   1170         expect("\\frac 2 3").toParse();
   1171     });
   1172 
   1173     it("should fail if there are not enough arguments", function() {
   1174         const missingGroups = [
   1175             "\\frac{x}",
   1176             "\\color{#fff}",
   1177             "\\rule{1em}",
   1178             "\\llap",
   1179             "\\bigl",
   1180             "\\text",
   1181         ];
   1182 
   1183         for (let i = 0; i < missingGroups.length; i++) {
   1184             expect(missingGroups[i]).toNotParse();
   1185         }
   1186     });
   1187 
   1188     it("should fail when there are missing sup/subscripts", function() {
   1189         expect("x^").toNotParse();
   1190         expect("x_").toNotParse();
   1191     });
   1192 
   1193     it("should fail when arguments require arguments", function() {
   1194         const badArguments = [
   1195             "\\frac \\frac x y z",
   1196             "\\frac x \\frac y z",
   1197             "\\frac \\sqrt x y",
   1198             "\\frac x \\sqrt y",
   1199             "\\frac \\llap x y",
   1200             "\\frac x \\llap y",
   1201             // This actually doesn't work in real TeX, but it is suprisingly
   1202             // hard to get this to correctly work. So, we take hit of very small
   1203             // amounts of non-compatiblity in order for the rest of the tests to
   1204             // work
   1205             // "\\llap \\frac x y",
   1206             "\\llap \\llap x",
   1207             "\\sqrt \\llap x",
   1208         ];
   1209 
   1210         for (let i = 0; i < badArguments.length; i++) {
   1211             expect(badArguments[i]).toNotParse();
   1212         }
   1213     });
   1214 
   1215     it("should work when the arguments have braces", function() {
   1216         const goodArguments = [
   1217             "\\frac {\\frac x y} z",
   1218             "\\frac x {\\frac y z}",
   1219             "\\frac {\\sqrt x} y",
   1220             "\\frac x {\\sqrt y}",
   1221             "\\frac {\\llap x} y",
   1222             "\\frac x {\\llap y}",
   1223             "\\llap {\\frac x y}",
   1224             "\\llap {\\llap x}",
   1225             "\\sqrt {\\llap x}",
   1226         ];
   1227 
   1228         for (let i = 0; i < goodArguments.length; i++) {
   1229             expect(goodArguments[i]).toParse();
   1230         }
   1231     });
   1232 
   1233     it("should fail when sup/subscripts require arguments", function() {
   1234         const badSupSubscripts = [
   1235             "x^\\sqrt x",
   1236             "x^\\llap x",
   1237             "x_\\sqrt x",
   1238             "x_\\llap x",
   1239         ];
   1240 
   1241         for (let i = 0; i < badSupSubscripts.length; i++) {
   1242             expect(badSupSubscripts[i]).toNotParse();
   1243         }
   1244     });
   1245 
   1246     it("should work when sup/subscripts arguments have braces", function() {
   1247         const goodSupSubscripts = [
   1248             "x^{\\sqrt x}",
   1249             "x^{\\llap x}",
   1250             "x_{\\sqrt x}",
   1251             "x_{\\llap x}",
   1252         ];
   1253 
   1254         for (let i = 0; i < goodSupSubscripts.length; i++) {
   1255             expect(goodSupSubscripts[i]).toParse();
   1256         }
   1257     });
   1258 
   1259     it("should parse multiple primes correctly", function() {
   1260         expect("x''''").toParse();
   1261         expect("x_2''").toParse();
   1262         expect("x''_2").toParse();
   1263     });
   1264 
   1265     it("should fail when sup/subscripts are interspersed with arguments", function() {
   1266         expect("\\sqrt^23").toNotParse();
   1267         expect("\\frac^234").toNotParse();
   1268         expect("\\frac2^34").toNotParse();
   1269     });
   1270 
   1271     it("should succeed when sup/subscripts come after whole functions", function() {
   1272         expect("\\sqrt2^3").toParse();
   1273         expect("\\frac23^4").toParse();
   1274     });
   1275 
   1276     it("should succeed with a sqrt around a text/frac", function() {
   1277         expect("\\sqrt \\frac x y").toParse();
   1278         expect("\\sqrt \\text x").toParse();
   1279         expect("x^\\frac x y").toParse();
   1280         expect("x_\\text x").toParse();
   1281     });
   1282 
   1283     it("should fail when arguments are \\left", function() {
   1284         const badLeftArguments = [
   1285             "\\frac \\left( x \\right) y",
   1286             "\\frac x \\left( y \\right)",
   1287             "\\llap \\left( x \\right)",
   1288             "\\sqrt \\left( x \\right)",
   1289             "x^\\left( x \\right)",
   1290         ];
   1291 
   1292         for (let i = 0; i < badLeftArguments.length; i++) {
   1293             expect(badLeftArguments[i]).toNotParse();
   1294         }
   1295     });
   1296 
   1297     it("should succeed when there are braces around the \\left/\\right", function() {
   1298         const goodLeftArguments = [
   1299             "\\frac {\\left( x \\right)} y",
   1300             "\\frac x {\\left( y \\right)}",
   1301             "\\llap {\\left( x \\right)}",
   1302             "\\sqrt {\\left( x \\right)}",
   1303             "x^{\\left( x \\right)}",
   1304         ];
   1305 
   1306         for (let i = 0; i < goodLeftArguments.length; i++) {
   1307             expect(goodLeftArguments[i]).toParse();
   1308         }
   1309     });
   1310 });
   1311 
   1312 describe("A style change parser", function() {
   1313     it("should not fail", function() {
   1314         expect("\\displaystyle x").toParse();
   1315         expect("\\textstyle x").toParse();
   1316         expect("\\scriptstyle x").toParse();
   1317         expect("\\scriptscriptstyle x").toParse();
   1318     });
   1319 
   1320     it("should produce the correct style", function() {
   1321         const displayParse = getParsed("\\displaystyle x")[0];
   1322         expect(displayParse.value.style).toEqual("display");
   1323 
   1324         const scriptscriptParse = getParsed("\\scriptscriptstyle x")[0];
   1325         expect(scriptscriptParse.value.style).toEqual("scriptscript");
   1326     });
   1327 
   1328     it("should only change the style within its group", function() {
   1329         const text = "a b { c d \\displaystyle e f } g h";
   1330         const parse = getParsed(text);
   1331 
   1332         const displayNode = parse[2].value[2];
   1333 
   1334         expect(displayNode.type).toEqual("styling");
   1335 
   1336         const displayBody = displayNode.value.value;
   1337 
   1338         expect(displayBody.length).toEqual(2);
   1339         expect(displayBody[0].value).toEqual("e");
   1340     });
   1341 });
   1342 
   1343 describe("A font parser", function() {
   1344     it("should parse \\mathrm, \\mathbb, and \\mathit", function() {
   1345         expect("\\mathrm x").toParse();
   1346         expect("\\mathbb x").toParse();
   1347         expect("\\mathit x").toParse();
   1348         expect("\\mathrm {x + 1}").toParse();
   1349         expect("\\mathbb {x + 1}").toParse();
   1350         expect("\\mathit {x + 1}").toParse();
   1351     });
   1352 
   1353     it("should parse \\mathcal and \\mathfrak", function() {
   1354         expect("\\mathcal{ABC123}").toParse();
   1355         expect("\\mathfrak{abcABC123}").toParse();
   1356     });
   1357 
   1358     it("should produce the correct fonts", function() {
   1359         const mathbbParse = getParsed("\\mathbb x")[0];
   1360         expect(mathbbParse.value.font).toEqual("mathbb");
   1361         expect(mathbbParse.value.type).toEqual("font");
   1362 
   1363         const mathrmParse = getParsed("\\mathrm x")[0];
   1364         expect(mathrmParse.value.font).toEqual("mathrm");
   1365         expect(mathrmParse.value.type).toEqual("font");
   1366 
   1367         const mathitParse = getParsed("\\mathit x")[0];
   1368         expect(mathitParse.value.font).toEqual("mathit");
   1369         expect(mathitParse.value.type).toEqual("font");
   1370 
   1371         const mathcalParse = getParsed("\\mathcal C")[0];
   1372         expect(mathcalParse.value.font).toEqual("mathcal");
   1373         expect(mathcalParse.value.type).toEqual("font");
   1374 
   1375         const mathfrakParse = getParsed("\\mathfrak C")[0];
   1376         expect(mathfrakParse.value.font).toEqual("mathfrak");
   1377         expect(mathfrakParse.value.type).toEqual("font");
   1378     });
   1379 
   1380     it("should parse nested font commands", function() {
   1381         const nestedParse = getParsed("\\mathbb{R \\neq \\mathrm{R}}")[0];
   1382         expect(nestedParse.value.font).toEqual("mathbb");
   1383         expect(nestedParse.value.type).toEqual("font");
   1384 
   1385         expect(nestedParse.value.body.value.length).toEqual(3);
   1386         const bbBody = nestedParse.value.body.value;
   1387         expect(bbBody[0].type).toEqual("mathord");
   1388         expect(bbBody[1].type).toEqual("rel");
   1389         expect(bbBody[2].type).toEqual("font");
   1390         expect(bbBody[2].value.font).toEqual("mathrm");
   1391         expect(bbBody[2].value.type).toEqual("font");
   1392     });
   1393 
   1394     it("should work with \\color", function() {
   1395         const colorMathbbParse = getParsed("\\color{blue}{\\mathbb R}")[0];
   1396         expect(colorMathbbParse.value.type).toEqual("color");
   1397         expect(colorMathbbParse.value.color).toEqual("blue");
   1398         const body = colorMathbbParse.value.value;
   1399         expect(body.length).toEqual(1);
   1400         expect(body[0].value.type).toEqual("font");
   1401         expect(body[0].value.font).toEqual("mathbb");
   1402     });
   1403 
   1404     it("should not parse a series of font commands", function() {
   1405         expect("\\mathbb \\mathrm R").toNotParse();
   1406     });
   1407 
   1408     it("should nest fonts correctly", function() {
   1409         const bf = getParsed("\\mathbf{a\\mathrm{b}c}")[0];
   1410         expect(bf.value.type).toEqual("font");
   1411         expect(bf.value.font).toEqual("mathbf");
   1412         expect(bf.value.body.value.length).toEqual(3);
   1413         expect(bf.value.body.value[0].value).toEqual("a");
   1414         expect(bf.value.body.value[1].value.type).toEqual("font");
   1415         expect(bf.value.body.value[1].value.font).toEqual("mathrm");
   1416         expect(bf.value.body.value[2].value).toEqual("c");
   1417     });
   1418 
   1419     it("should have the correct greediness", function() {
   1420         expect("e^\\mathbf{x}").toParse();
   1421     });
   1422 });
   1423 
   1424 describe("An HTML font tree-builder", function() {
   1425     it("should render \\mathbb{R} with the correct font", function() {
   1426         const markup = katex.renderToString("\\mathbb{R}");
   1427         expect(markup).toContain("<span class=\"mord mathbb\">R</span>");
   1428     });
   1429 
   1430     it("should render \\mathrm{R} with the correct font", function() {
   1431         const markup = katex.renderToString("\\mathrm{R}");
   1432         expect(markup).toContain("<span class=\"mord mathrm\">R</span>");
   1433     });
   1434 
   1435     it("should render \\mathcal{R} with the correct font", function() {
   1436         const markup = katex.renderToString("\\mathcal{R}");
   1437         expect(markup).toContain("<span class=\"mord mathcal\">R</span>");
   1438     });
   1439 
   1440     it("should render \\mathfrak{R} with the correct font", function() {
   1441         const markup = katex.renderToString("\\mathfrak{R}");
   1442         expect(markup).toContain("<span class=\"mord mathfrak\">R</span>");
   1443     });
   1444 
   1445     it("should render \\text{R} with the correct font", function() {
   1446         const markup = katex.renderToString("\\text{R}");
   1447         expect(markup).toContain("<span class=\"mord mathrm\">R</span>");
   1448     });
   1449 
   1450     it("should render \\textit{R} with the correct font", function() {
   1451         const markup = katex.renderToString("\\textit{R}");
   1452         expect(markup).toContain("<span class=\"mord textit\">R</span>");
   1453     });
   1454 
   1455     it("should render \\text{\\textit{R}} with the correct font", function() {
   1456         const markup = katex.renderToString("\\text{\\textit{R}}");
   1457         expect(markup).toContain("<span class=\"mord textit\">R</span>");
   1458     });
   1459 
   1460     it("should render \\text{R\\textit{S}T} with the correct fonts", function() {
   1461         const markup = katex.renderToString("\\text{R\\textit{S}T}");
   1462         expect(markup).toContain("<span class=\"mord mathrm\">R</span>");
   1463         expect(markup).toContain("<span class=\"mord textit\">S</span>");
   1464         expect(markup).toContain("<span class=\"mord mathrm\">T</span>");
   1465     });
   1466 
   1467     it("should render \\textbf{R} with the correct font", function() {
   1468         const markup = katex.renderToString("\\textbf{R}");
   1469         expect(markup).toContain("<span class=\"mord mathbf\">R</span>");
   1470     });
   1471 
   1472     it("should render \\textsf{R} with the correct font", function() {
   1473         const markup = katex.renderToString("\\textsf{R}");
   1474         expect(markup).toContain("<span class=\"mord mathsf\">R</span>");
   1475     });
   1476 
   1477     it("should render \\texttt{R} with the correct font", function() {
   1478         const markup = katex.renderToString("\\texttt{R}");
   1479         expect(markup).toContain("<span class=\"mord mathtt\">R</span>");
   1480     });
   1481 
   1482     it("should render a combination of font and color changes", function() {
   1483         let markup = katex.renderToString("\\color{blue}{\\mathbb R}");
   1484         let span = "<span class=\"mord mathbb\" style=\"color:blue;\">R</span>";
   1485         expect(markup).toContain(span);
   1486 
   1487         markup = katex.renderToString("\\mathbb{\\color{blue}{R}}");
   1488         span = "<span class=\"mord mathbb\" style=\"color:blue;\">R</span>";
   1489         expect(markup).toContain(span);
   1490     });
   1491 
   1492     it("should throw TypeError when the expression is of the wrong type", function() {
   1493         expect(function() {
   1494             katex.renderToString({badInputType: "yes"});
   1495         }).toThrowError(TypeError);
   1496         expect(function() {
   1497             katex.renderToString([1, 2]);
   1498         }).toThrowError(TypeError);
   1499         expect(function() {
   1500             katex.renderToString(undefined);
   1501         }).toThrowError(TypeError);
   1502         expect(function() {
   1503             katex.renderToString(null);
   1504         }).toThrowError(TypeError);
   1505         expect(function() {
   1506             katex.renderToString(1.234);
   1507         }).toThrowError(TypeError);
   1508     });
   1509 
   1510     it("should not throw TypeError when the expression is a supported type", function() {
   1511         expect(function() {
   1512             katex.renderToString("\\sqrt{123}");
   1513         }).not.toThrowError(TypeError);
   1514         expect(function() {
   1515             katex.renderToString(new String("\\sqrt{123}"));
   1516         }).not.toThrowError(TypeError);
   1517     });
   1518 });
   1519 
   1520 
   1521 describe("A MathML font tree-builder", function() {
   1522     const contents = "Ax2k\\omega\\Omega\\imath+";
   1523 
   1524     it("should render " + contents + " with the correct mathvariants", function() {
   1525         const tree = getParsed(contents);
   1526         const markup = buildMathML(tree, contents, defaultOptions).toMarkup();
   1527         expect(markup).toContain("<mi>A</mi>");
   1528         expect(markup).toContain("<mi>x</mi>");
   1529         expect(markup).toContain("<mn>2</mn>");
   1530         expect(markup).toContain("<mi>\u03c9</mi>");   // \omega
   1531         expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>");   // \Omega
   1532         expect(markup).toContain("<mi>\u0131</mi>");   // \imath
   1533         expect(markup).toContain("<mo>+</mo>");
   1534     });
   1535 
   1536     it("should render \\mathbb{" + contents + "} with the correct mathvariants", function() {
   1537         const tex = "\\mathbb{" + contents + "}";
   1538         const tree = getParsed(tex);
   1539         const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
   1540         expect(markup).toContain("<mi mathvariant=\"double-struck\">A</mi>");
   1541         expect(markup).toContain("<mi>x</mi>");
   1542         expect(markup).toContain("<mn mathvariant=\"normal\">2</mn>");
   1543         expect(markup).toContain("<mi>\u03c9</mi>");                        // \omega
   1544         expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
   1545         expect(markup).toContain("<mi>\u0131</mi>");                        // \imath
   1546         expect(markup).toContain("<mo>+</mo>");
   1547     });
   1548 
   1549     it("should render \\mathrm{" + contents + "} with the correct mathvariants", function() {
   1550         const tex = "\\mathrm{" + contents + "}";
   1551         const tree = getParsed(tex);
   1552         const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
   1553         expect(markup).toContain("<mi mathvariant=\"normal\">A</mi>");
   1554         expect(markup).toContain("<mi mathvariant=\"normal\">x</mi>");
   1555         expect(markup).toContain("<mn mathvariant=\"normal\">2</mn>");
   1556         expect(markup).toContain("<mi>\u03c9</mi>");   // \omega
   1557         expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>");   // \Omega
   1558         expect(markup).toContain("<mi>\u0131</mi>");   // \imath
   1559         expect(markup).toContain("<mo>+</mo>");
   1560     });
   1561 
   1562     it("should render \\mathit{" + contents + "} with the correct mathvariants", function() {
   1563         const tex = "\\mathit{" + contents + "}";
   1564         const tree = getParsed(tex);
   1565         const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
   1566         expect(markup).toContain("<mi mathvariant=\"italic\">A</mi>");
   1567         expect(markup).toContain("<mi mathvariant=\"italic\">x</mi>");
   1568         expect(markup).toContain("<mn mathvariant=\"italic\">2</mn>");
   1569         expect(markup).toContain("<mi mathvariant=\"italic\">\u03c9</mi>");   // \omega
   1570         expect(markup).toContain("<mi mathvariant=\"italic\">\u03A9</mi>");   // \Omega
   1571         expect(markup).toContain("<mi mathvariant=\"italic\">\u0131</mi>");   // \imath
   1572         expect(markup).toContain("<mo>+</mo>");
   1573     });
   1574 
   1575     it("should render \\mathbf{" + contents + "} with the correct mathvariants", function() {
   1576         const tex = "\\mathbf{" + contents + "}";
   1577         const tree = getParsed(tex);
   1578         const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
   1579         expect(markup).toContain("<mi mathvariant=\"bold\">A</mi>");
   1580         expect(markup).toContain("<mi mathvariant=\"bold\">x</mi>");
   1581         expect(markup).toContain("<mn mathvariant=\"bold\">2</mn>");
   1582         expect(markup).toContain("<mi>\u03c9</mi>");                        // \omega
   1583         expect(markup).toContain("<mi mathvariant=\"bold\">\u03A9</mi>");   // \Omega
   1584         expect(markup).toContain("<mi>\u0131</mi>");                        // \imath
   1585         expect(markup).toContain("<mo>+</mo>");
   1586     });
   1587 
   1588     it("should render \\mathcal{" + contents + "} with the correct mathvariants", function() {
   1589         const tex = "\\mathcal{" + contents + "}";
   1590         const tree = getParsed(tex);
   1591         const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
   1592         expect(markup).toContain("<mi mathvariant=\"script\">A</mi>");
   1593         expect(markup).toContain("<mi>x</mi>");                             // script is caps only
   1594         expect(markup).toContain("<mn mathvariant=\"script\">2</mn>");
   1595         // MathJax marks everything below as "script" except \omega
   1596         // We don't have these glyphs in "caligraphic" and neither does MathJax
   1597         expect(markup).toContain("<mi>\u03c9</mi>");                        // \omega
   1598         expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
   1599         expect(markup).toContain("<mi>\u0131</mi>");                        // \imath
   1600         expect(markup).toContain("<mo>+</mo>");
   1601     });
   1602 
   1603     it("should render \\mathfrak{" + contents + "} with the correct mathvariants", function() {
   1604         const tex = "\\mathfrak{" + contents + "}";
   1605         const tree = getParsed(tex);
   1606         const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
   1607         expect(markup).toContain("<mi mathvariant=\"fraktur\">A</mi>");
   1608         expect(markup).toContain("<mi mathvariant=\"fraktur\">x</mi>");
   1609         expect(markup).toContain("<mn mathvariant=\"fraktur\">2</mn>");
   1610         // MathJax marks everything below as "fraktur" except \omega
   1611         // We don't have these glyphs in "fraktur" and neither does MathJax
   1612         expect(markup).toContain("<mi>\u03c9</mi>");                        // \omega
   1613         expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
   1614         expect(markup).toContain("<mi>\u0131</mi>");                        // \imath
   1615         expect(markup).toContain("<mo>+</mo>");
   1616     });
   1617 
   1618     it("should render \\mathscr{" + contents + "} with the correct mathvariants", function() {
   1619         const tex = "\\mathscr{" + contents + "}";
   1620         const tree = getParsed(tex);
   1621         const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
   1622         expect(markup).toContain("<mi mathvariant=\"script\">A</mi>");
   1623         // MathJax marks everything below as "script" except \omega
   1624         // We don't have these glyphs in "script" and neither does MathJax
   1625         expect(markup).toContain("<mi>x</mi>");
   1626         expect(markup).toContain("<mn mathvariant=\"normal\">2</mn>");
   1627         expect(markup).toContain("<mi>\u03c9</mi>");                        // \omega
   1628         expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
   1629         expect(markup).toContain("<mi>\u0131</mi>");                        // \imath
   1630         expect(markup).toContain("<mo>+</mo>");
   1631     });
   1632 
   1633     it("should render \\mathsf{" + contents + "} with the correct mathvariants", function() {
   1634         const tex = "\\mathsf{" + contents + "}";
   1635         const tree = getParsed(tex);
   1636         const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
   1637         expect(markup).toContain("<mi mathvariant=\"sans-serif\">A</mi>");
   1638         expect(markup).toContain("<mi mathvariant=\"sans-serif\">x</mi>");
   1639         expect(markup).toContain("<mn mathvariant=\"sans-serif\">2</mn>");
   1640         expect(markup).toContain("<mi>\u03c9</mi>");                            // \omega
   1641         expect(markup).toContain("<mi mathvariant=\"sans-serif\">\u03A9</mi>"); // \Omega
   1642         expect(markup).toContain("<mi>\u0131</mi>");                            // \imath
   1643         expect(markup).toContain("<mo>+</mo>");
   1644     });
   1645 
   1646     it("should render a combination of font and color changes", function() {
   1647         let tex = "\\color{blue}{\\mathbb R}";
   1648         let tree = getParsed(tex);
   1649         let markup = buildMathML(tree, tex, defaultOptions).toMarkup();
   1650         let node = "<mstyle mathcolor=\"blue\">" +
   1651             "<mi mathvariant=\"double-struck\">R</mi>" +
   1652             "</mstyle>";
   1653         expect(markup).toContain(node);
   1654 
   1655         // reverse the order of the commands
   1656         tex = "\\mathbb{\\color{blue}{R}}";
   1657         tree = getParsed(tex);
   1658         markup = buildMathML(tree, tex, defaultOptions).toMarkup();
   1659         node = "<mstyle mathcolor=\"blue\">" +
   1660             "<mi mathvariant=\"double-struck\">R</mi>" +
   1661             "</mstyle>";
   1662         expect(markup).toContain(node);
   1663     });
   1664 });
   1665 
   1666 describe("A bin builder", function() {
   1667     it("should create mbins normally", function() {
   1668         const built = getBuilt("x + y");
   1669 
   1670         expect(built[1].classes).toContain("mbin");
   1671     });
   1672 
   1673     it("should create ords when at the beginning of lists", function() {
   1674         const built = getBuilt("+ x");
   1675 
   1676         expect(built[0].classes).toContain("mord");
   1677         expect(built[0].classes).not.toContain("mbin");
   1678     });
   1679 
   1680     it("should create ords after some other objects", function() {
   1681         expect(getBuilt("x + + 2")[2].classes).toContain("mord");
   1682         expect(getBuilt("( + 2")[1].classes).toContain("mord");
   1683         expect(getBuilt("= + 2")[1].classes).toContain("mord");
   1684         expect(getBuilt("\\sin + 2")[1].classes).toContain("mord");
   1685         expect(getBuilt(", + 2")[1].classes).toContain("mord");
   1686     });
   1687 
   1688     it("should correctly interact with color objects", function() {
   1689         expect(getBuilt("\\blue{x}+y")[1].classes).toContain("mbin");
   1690         expect(getBuilt("\\blue{x+}+y")[1].classes).toContain("mbin");
   1691         expect(getBuilt("\\blue{x+}+y")[2].classes).toContain("mord");
   1692     });
   1693 });
   1694 
   1695 describe("A markup generator", function() {
   1696     it("marks trees up", function() {
   1697         // Just a few quick sanity checks here...
   1698         const markup = katex.renderToString("\\sigma^2");
   1699         expect(markup.indexOf("<span")).toBe(0);
   1700         expect(markup).toContain("\u03c3");  // sigma
   1701         expect(markup).toContain("margin-right");
   1702         expect(markup).not.toContain("marginRight");
   1703     });
   1704 
   1705     it("generates both MathML and HTML", function() {
   1706         const markup = katex.renderToString("a");
   1707 
   1708         expect(markup).toContain("<span");
   1709         expect(markup).toContain("<math");
   1710     });
   1711 });
   1712 
   1713 describe("A parse tree generator", function() {
   1714     it("generates a tree", function() {
   1715         const tree = stripPositions(katex.__parse("\\sigma^2"));
   1716         expect(JSON.stringify(tree)).toEqual(JSON.stringify([
   1717             {
   1718                 "type": "supsub",
   1719                 "value": {
   1720                     "base": {
   1721                         "type": "mathord",
   1722                         "value": "\\sigma",
   1723                         "mode": "math",
   1724                     },
   1725                     "sup": {
   1726                         "type": "textord",
   1727                         "value": "2",
   1728                         "mode": "math",
   1729                     },
   1730                     "sub": undefined,
   1731                 },
   1732                 "mode": "math",
   1733             },
   1734         ]));
   1735     });
   1736 });
   1737 
   1738 describe("An accent parser", function() {
   1739     it("should not fail", function() {
   1740         expect("\\vec{x}").toParse();
   1741         expect("\\vec{x^2}").toParse();
   1742         expect("\\vec{x}^2").toParse();
   1743         expect("\\vec x").toParse();
   1744     });
   1745 
   1746     it("should produce accents", function() {
   1747         const parse = getParsed("\\vec x")[0];
   1748 
   1749         expect(parse.type).toEqual("accent");
   1750     });
   1751 
   1752     it("should be grouped more tightly than supsubs", function() {
   1753         const parse = getParsed("\\vec x^2")[0];
   1754 
   1755         expect(parse.type).toEqual("supsub");
   1756     });
   1757 
   1758     it("should not parse expanding accents", function() {
   1759         expect("\\widehat{x}").toNotParse();
   1760     });
   1761 });
   1762 
   1763 describe("An accent builder", function() {
   1764     it("should not fail", function() {
   1765         expect("\\vec{x}").toBuild();
   1766         expect("\\vec{x}^2").toBuild();
   1767         expect("\\vec{x}_2").toBuild();
   1768         expect("\\vec{x}_2^2").toBuild();
   1769     });
   1770 
   1771     it("should produce mords", function() {
   1772         expect(getBuilt("\\vec x")[0].classes).toContain("mord");
   1773         expect(getBuilt("\\vec +")[0].classes).toContain("mord");
   1774         expect(getBuilt("\\vec +")[0].classes).not.toContain("mbin");
   1775         expect(getBuilt("\\vec )^2")[0].classes).toContain("mord");
   1776         expect(getBuilt("\\vec )^2")[0].classes).not.toContain("mclose");
   1777     });
   1778 });
   1779 
   1780 describe("A phantom parser", function() {
   1781     it("should not fail", function() {
   1782         expect("\\phantom{x}").toParse();
   1783         expect("\\phantom{x^2}").toParse();
   1784         expect("\\phantom{x}^2").toParse();
   1785         expect("\\phantom x").toParse();
   1786     });
   1787 
   1788     it("should build a phantom node", function() {
   1789         const parse = getParsed("\\phantom{x}")[0];
   1790 
   1791         expect(parse.type).toEqual("phantom");
   1792         expect(parse.value.value).toBeDefined();
   1793     });
   1794 });
   1795 
   1796 describe("A phantom builder", function() {
   1797     it("should not fail", function() {
   1798         expect("\\phantom{x}").toBuild();
   1799         expect("\\phantom{x^2}").toBuild();
   1800         expect("\\phantom{x}^2").toBuild();
   1801         expect("\\phantom x").toBuild();
   1802     });
   1803 
   1804     it("should make the children transparent", function() {
   1805         const children = getBuilt("\\phantom{x+1}");
   1806         expect(children[0].style.color).toBe("transparent");
   1807         expect(children[1].style.color).toBe("transparent");
   1808         expect(children[2].style.color).toBe("transparent");
   1809     });
   1810 
   1811     it("should make all descendants transparent", function() {
   1812         const children = getBuilt("\\phantom{x+\\blue{1}}");
   1813         expect(children[0].style.color).toBe("transparent");
   1814         expect(children[1].style.color).toBe("transparent");
   1815         expect(children[2].style.color).toBe("transparent");
   1816     });
   1817 });
   1818 
   1819 describe("A parser error", function() {
   1820     it("should report the position of an error", function() {
   1821         try {
   1822             parseTree("\\sqrt}", defaultSettings);
   1823         } catch (e) {
   1824             expect(e.position).toEqual(5);
   1825         }
   1826     });
   1827 });
   1828 
   1829 describe("An optional argument parser", function() {
   1830     it("should not fail", function() {
   1831         // Note this doesn't actually make an optional argument, but still
   1832         // should work
   1833         expect("\\frac[1]{2}{3}").toParse();
   1834 
   1835         expect("\\rule[0.2em]{1em}{1em}").toParse();
   1836     });
   1837 
   1838     it("should work with sqrts with optional arguments", function() {
   1839         expect("\\sqrt[3]{2}").toParse();
   1840     });
   1841 
   1842     it("should work when the optional argument is missing", function() {
   1843         expect("\\sqrt{2}").toParse();
   1844         expect("\\rule{1em}{2em}").toParse();
   1845     });
   1846 
   1847     it("should fail when the optional argument is malformed", function() {
   1848         expect("\\rule[1]{2em}{3em}").toNotParse();
   1849     });
   1850 
   1851     it("should not work if the optional argument isn't closed", function() {
   1852         expect("\\sqrt[").toNotParse();
   1853     });
   1854 });
   1855 
   1856 describe("An array environment", function() {
   1857 
   1858     it("should accept a single alignment character", function() {
   1859         const parse = getParsed("\\begin{array}r1\\\\20\\end{array}");
   1860         expect(parse[0].type).toBe("array");
   1861         expect(parse[0].value.cols).toEqual([
   1862             { type: "align", align: "r" },
   1863         ]);
   1864     });
   1865 
   1866     it("should accept vertical separators", function() {
   1867         const parse = getParsed("\\begin{array}{|l||c|}\\end{array}");
   1868         expect(parse[0].type).toBe("array");
   1869         expect(parse[0].value.cols).toEqual([
   1870             { type: "separator", separator: "|" },
   1871             { type: "align", align: "l" },
   1872             { type: "separator", separator: "|" },
   1873             { type: "separator", separator: "|" },
   1874             { type: "align", align: "c" },
   1875             { type: "separator", separator: "|" },
   1876         ]);
   1877     });
   1878 
   1879 });
   1880 
   1881 describe("A cases environment", function() {
   1882 
   1883     it("should parse its input", function() {
   1884         expect("f(a,b)=\\begin{cases}a+1&\\text{if }b\\text{ is odd}\\\\" +
   1885                "a&\\text{if }b=0\\\\a-1&\\text{otherwise}\\end{cases}")
   1886             .toParse();
   1887     });
   1888 
   1889 });
   1890 
   1891 describe("An aligned environment", function() {
   1892 
   1893     it("should parse its input", function() {
   1894         expect("\\begin{aligned}a&=b&c&=d\\\\e&=f\\end{aligned}")
   1895             .toParse();
   1896     });
   1897 
   1898 });
   1899 
   1900 const getMathML = function(expr, settings) {
   1901     const usedSettings = settings ? settings : defaultSettings;
   1902 
   1903     expect(expr).toParse(usedSettings);
   1904 
   1905     const built = buildMathML(parseTree(expr, usedSettings), expr, usedSettings);
   1906 
   1907     // Strip off the surrounding <span>
   1908     return built.children[0];
   1909 };
   1910 
   1911 describe("A MathML builder", function() {
   1912     it("should generate math nodes", function() {
   1913         const node = getMathML("x^2");
   1914 
   1915         expect(node.type).toEqual("math");
   1916     });
   1917 
   1918     it("should generate appropriate MathML types", function() {
   1919         const identifier = getMathML("x").children[0].children[0];
   1920         expect(identifier.children[0].type).toEqual("mi");
   1921 
   1922         const number = getMathML("1").children[0].children[0];
   1923         expect(number.children[0].type).toEqual("mn");
   1924 
   1925         const operator = getMathML("+").children[0].children[0];
   1926         expect(operator.children[0].type).toEqual("mo");
   1927 
   1928         const space = getMathML("\\;").children[0].children[0];
   1929         expect(space.children[0].type).toEqual("mspace");
   1930 
   1931         const text = getMathML("\\text{a}").children[0].children[0];
   1932         expect(text.children[0].type).toEqual("mtext");
   1933 
   1934         const textop = getMathML("\\sin").children[0].children[0];
   1935         expect(textop.children[0].type).toEqual("mi");
   1936     });
   1937 
   1938     it("should generate a <mphantom> node for \\phantom", function() {
   1939         const phantom = getMathML("\\phantom{x}").children[0].children[0];
   1940         expect(phantom.children[0].type).toEqual("mphantom");
   1941     });
   1942 });
   1943 
   1944 describe("A parser that does not throw on unsupported commands", function() {
   1945     // The parser breaks on unsupported commands unless it is explicitly
   1946     // told not to
   1947     const errorColor = "#933";
   1948     const noThrowSettings = new Settings({
   1949         throwOnError: false,
   1950         errorColor: errorColor,
   1951     });
   1952 
   1953     it("should still parse on unrecognized control sequences", function() {
   1954         expect("\\error").toParse(noThrowSettings);
   1955     });
   1956 
   1957     describe("should allow unrecognized controls sequences anywhere, including", function() {
   1958         it("in superscripts and subscripts", function() {
   1959             expect("2_\\error").toBuild(noThrowSettings);
   1960             expect("3^{\\error}_\\error").toBuild(noThrowSettings);
   1961             expect("\\int\\nolimits^\\error_\\error").toBuild(noThrowSettings);
   1962         });
   1963 
   1964         it("in fractions", function() {
   1965             expect("\\frac{345}{\\error}").toBuild(noThrowSettings);
   1966             expect("\\frac\\error{\\error}").toBuild(noThrowSettings);
   1967         });
   1968 
   1969         it("in square roots", function() {
   1970             expect("\\sqrt\\error").toBuild(noThrowSettings);
   1971             expect("\\sqrt{234\\error}").toBuild(noThrowSettings);
   1972         });
   1973 
   1974         it("in text boxes", function() {
   1975             expect("\\text{\\error}").toBuild(noThrowSettings);
   1976         });
   1977     });
   1978 
   1979     it("should produce color nodes with a color value given by errorColor", function() {
   1980         const parsedInput = getParsed("\\error", noThrowSettings);
   1981         expect(parsedInput[0].type).toBe("color");
   1982         expect(parsedInput[0].value.color).toBe(errorColor);
   1983     });
   1984 });
   1985 
   1986 describe("The symbol table integraty", function() {
   1987     it("should treat certain symbols as synonyms", function() {
   1988         expect(getBuilt("<")).toEqual(getBuilt("\\lt"));
   1989         expect(getBuilt(">")).toEqual(getBuilt("\\gt"));
   1990         expect(getBuilt("\\left<\\frac{1}{x}\\right>"))
   1991             .toEqual(getBuilt("\\left\\lt\\frac{1}{x}\\right\\gt"));
   1992     });
   1993 });
   1994 
   1995 describe("A macro expander", function() {
   1996 
   1997     const compareParseTree = function(actual, expected, macros) {
   1998         const settings = new Settings({macros: macros});
   1999         actual = stripPositions(parseTree(actual, settings));
   2000         expected = stripPositions(parseTree(expected, defaultSettings));
   2001         expect(actual).toEqual(expected);
   2002     };
   2003 
   2004     it("should produce individual tokens", function() {
   2005         compareParseTree("e^\\foo", "e^1 23", {"\\foo": "123"});
   2006     });
   2007 
   2008     it("should allow for multiple expansion", function() {
   2009         compareParseTree("1\\foo2", "1aa2", {
   2010             "\\foo": "\\bar\\bar",
   2011             "\\bar": "a",
   2012         });
   2013     });
   2014 
   2015     it("should expand the \overset macro as expected", function() {
   2016         expect("\\overset?=").toParseLike("\\mathop{=}\\limits^{?}");
   2017         expect("\\overset{x=y}{\sqrt{ab}}")
   2018             .toParseLike("\\mathop{\sqrt{ab}}\\limits^{x=y}");
   2019         expect("\\overset {?} =").toParseLike("\\mathop{=}\\limits^{?}");
   2020     });
   2021 });
   2022 
   2023 describe("A parser taking String objects", function() {
   2024     it("should not fail on an empty String object", function() {
   2025         expect(new String("")).toParse();
   2026     });
   2027 
   2028     it("should parse the same as a regular string", function() {
   2029         expect(new String("xy")).toParseLike("xy");
   2030         expect(new String("\\div")).toParseLike("\\div");
   2031         expect(new String("\\frac 1 2")).toParseLike("\\frac 1 2");
   2032     });
   2033 });