/* eslint max-len:0 */ /* global expect: false */ /* global it: false */ /* global describe: false */ /* global beforeAll: false */ import buildMathML from "../src/buildMathML"; import buildTree from "../src/buildTree"; import katex from "../katex"; import parseTree from "../src/parseTree"; import Options from "../src/Options"; import Settings from "../src/Settings"; import Style from "../src/Style"; import { strictSettings, nonstrictSettings, r, getBuilt, getParsed, stripPositions, } from "./helpers"; const defaultOptions = new Options({ style: Style.TEXT, size: 5, maxSize: Infinity, }); describe("A parser", function() { it("should not fail on an empty string", function() { expect``.toParse(strictSettings); }); it("should ignore whitespace", function() { expect` x y `.toParseLike("xy", strictSettings); }); it("should ignore whitespace in atom", function() { expect` x ^ y `.toParseLike("x^y", strictSettings); }); }); describe("An ord parser", function() { const expression = "1234|/@.\"`abcdefgzABCDEFGZ"; it("should not fail", function() { expect(expression).toParse(); }); it("should build a list of ords", function() { const parse = getParsed(expression); for (let i = 0; i < parse.length; i++) { const group = parse[i]; expect(group.type).toMatch("ord"); } }); it("should parse the right number of ords", function() { const parse = getParsed(expression); expect(parse).toHaveLength(expression.length); }); }); describe("A bin parser", function() { const expression = r`+-*\cdot\pm\div`; it("should not fail", function() { expect(expression).toParse(); }); it("should build a list of bins", function() { const parse = getParsed(expression); for (let i = 0; i < parse.length; i++) { const group = parse[i]; expect(group.type).toEqual("atom"); expect(group.family).toEqual("bin"); } }); }); describe("A rel parser", function() { const expression = r`=<>\leq\geq\neq\nleq\ngeq\cong`; const notExpression = r`\not=\not<\not>\not\leq\not\geq\not\in`; it("should not fail", function() { expect(expression).toParse(); expect(notExpression).toParse(); }); it("should build a list of rels", function() { const parse = getParsed(expression); for (let i = 0; i < parse.length; i++) { let group = parse[i]; if (group.type === "htmlmathml") { expect(group.html).toHaveLength(1); group = group.html[0]; } if (group.type === "mclass") { expect(group.mclass).toEqual("mrel"); } else { expect(group.type).toEqual("atom"); expect(group.family).toEqual("rel"); } } }); }); describe("A punct parser", function() { const expression = ",;"; it("should not fail", function() { expect(expression).toParse(strictSettings); }); it("should build a list of puncts", function() { const parse = getParsed(expression); for (let i = 0; i < parse.length; i++) { const group = parse[i]; expect(group.type).toEqual("atom"); expect(group.family).toEqual("punct"); } }); }); describe("An open parser", function() { const expression = "(["; it("should not fail", function() { expect(expression).toParse(); }); it("should build a list of opens", function() { const parse = getParsed(expression); for (let i = 0; i < parse.length; i++) { const group = parse[i]; expect(group.type).toEqual("atom"); expect(group.family).toEqual("open"); } }); }); describe("A close parser", function() { const expression = ")]?!"; it("should not fail", function() { expect(expression).toParse(); }); it("should build a list of closes", function() { const parse = getParsed(expression); for (let i = 0; i < parse.length; i++) { const group = parse[i]; expect(group.type).toEqual("atom"); expect(group.family).toEqual("close"); } }); }); describe("A \\KaTeX parser", function() { it("should not fail", function() { expect`\KaTeX`.toParse(); }); }); describe("A subscript and superscript parser", function() { it("should not fail on superscripts", function() { expect`x^2`.toParse(); }); it("should not fail on subscripts", function() { expect`x_3`.toParse(); }); it("should not fail on both subscripts and superscripts", function() { expect`x^2_3`.toParse(); expect`x_2^3`.toParse(); }); it("should not fail when there is no nucleus", function() { expect`^3`.toParse(); expect`^3+`.toParse(); expect`_2`.toParse(); expect`^3_2`.toParse(); expect`_2^3`.toParse(); }); it("should produce supsubs for superscript", function() { const parse = getParsed`x^2`[0]; expect(parse.type).toBe("supsub"); expect(parse.base).toBeDefined(); expect(parse.sup).toBeDefined(); expect(parse.sub).toBeUndefined(); }); it("should produce supsubs for subscript", function() { const parse = getParsed`x_3`[0]; expect(parse.type).toBe("supsub"); expect(parse.base).toBeDefined(); expect(parse.sub).toBeDefined(); expect(parse.sup).toBeUndefined(); }); it("should produce supsubs for ^_", function() { const parse = getParsed`x^2_3`[0]; expect(parse.type).toBe("supsub"); expect(parse.base).toBeDefined(); expect(parse.sup).toBeDefined(); expect(parse.sub).toBeDefined(); }); it("should produce supsubs for _^", function() { const parse = getParsed`x_3^2`[0]; expect(parse.type).toBe("supsub"); expect(parse.base).toBeDefined(); expect(parse.sup).toBeDefined(); expect(parse.sub).toBeDefined(); }); it("should produce the same thing regardless of order", function() { expect`x^2_3`.toParseLike`x_3^2`; }); it("should not parse double subscripts or superscripts", function() { expect`x^x^x`.not.toParse(); expect`x_x_x`.not.toParse(); expect`x_x^x_x`.not.toParse(); expect`x_x^x^x`.not.toParse(); expect`x^x_x_x`.not.toParse(); expect`x^x_x^x`.not.toParse(); }); it("should work correctly with {}s", function() { expect`x^{2+3}`.toParse(); expect`x_{3-2}`.toParse(); expect`x^{2+3}_3`.toParse(); expect`x^2_{3-2}`.toParse(); expect`x^{2+3}_{3-2}`.toParse(); expect`x_{3-2}^{2+3}`.toParse(); expect`x_3^{2+3}`.toParse(); expect`x_{3-2}^2`.toParse(); }); it("should work with nested super/subscripts", function() { expect`x^{x^x}`.toParse(); expect`x^{x_x}`.toParse(); expect`x_{x^x}`.toParse(); expect`x_{x_x}`.toParse(); }); }); describe("A subscript and superscript tree-builder", function() { it("should not fail when there is no nucleus", function() { expect`^3`.toBuild(); expect`_2`.toBuild(); expect`^3_2`.toBuild(); expect`_2^3`.toBuild(); }); }); describe("A parser with limit controls", function() { it("should fail when the limit control is not preceded by an op node", function() { expect`3\nolimits_2^2`.not.toParse(); expect`\sqrt\limits_2^2`.not.toParse(); expect`45 +\nolimits 45`.not.toParse(); }); it("should parse when the limit control directly follows an op node", function() { expect`\int\limits_2^2 3`.toParse(); expect`\sum\nolimits_3^4 4`.toParse(); }); it("should parse when the limit control is in the sup/sub area of an op node", function() { expect`\int_2^2\limits`.toParse(); expect`\int^2\nolimits_2`.toParse(); expect`\int_2\limits^2`.toParse(); }); it("should allow multiple limit controls in the sup/sub area of an op node", function() { expect`\int_2\nolimits^2\limits 3`.toParse(); expect`\int\nolimits\limits_2^2`.toParse(); expect`\int\limits\limits\limits_2^2`.toParse(); }); it("should have the rightmost limit control determine the limits property " + "of the preceding op node", function() { let parsedInput = getParsed`\int\nolimits\limits_2^2`; expect(parsedInput[0].base.limits).toBe(true); parsedInput = getParsed`\int\limits_2\nolimits^2`; expect(parsedInput[0].base.limits).toBe(false); }); }); describe("A group parser", function() { it("should not fail", function() { expect`{xy}`.toParse(); }); it("should produce a single ord", function() { const parse = getParsed`{xy}`; expect(parse).toHaveLength(1); const ord = parse[0]; expect(ord.type).toMatch("ord"); expect(ord.body).toBeTruthy(); }); }); describe("A \\begingroup...\\endgroup parser", function() { it("should not fail", function() { expect`\begingroup xy \endgroup`.toParse(); }); it("should fail when it is mismatched", function() { expect`\begingroup xy`.not.toParse(); expect`\begingroup xy }`.not.toParse(); }); it("should produce a semi-simple group", function() { const parse = getParsed`\begingroup xy \endgroup`; expect(parse).toHaveLength(1); const ord = parse[0]; expect(ord.type).toMatch("ord"); expect(ord.body).toBeTruthy(); expect(ord.semisimple).toBeTruthy(); }); it("should not affect spacing in math mode", function() { expect`\begingroup x+ \endgroup y`.toBuildLike`x+y`; }); }); describe("An implicit group parser", function() { it("should not fail", function() { expect`\Large x`.toParse(); expect`abc {abc \Large xyz} abc`.toParse(); }); it("should produce a single object", function() { const parse = getParsed`\Large abc`; expect(parse).toHaveLength(1); const sizing = parse[0]; expect(sizing.type).toEqual("sizing"); expect(sizing.body).toBeTruthy(); expect(sizing.size).toBeDefined(); }); it("should apply only after the function", function() { const parse = getParsed`a \Large abc`; expect(parse).toHaveLength(2); const sizing = parse[1]; expect(sizing.type).toEqual("sizing"); expect(sizing.body).toHaveLength(3); }); it("should stop at the ends of groups", function() { const parse = getParsed`a { b \Large c } d`; const group = parse[1]; const sizing = group.body[1]; expect(sizing.type).toEqual("sizing"); expect(sizing.body).toHaveLength(1); }); describe("within optional groups", () => { it("should work with sizing commands: \\sqrt[\\small 3]{x}", () => { const tree = stripPositions(getParsed`\sqrt[\small 3]{x}`); expect(tree).toMatchSnapshot(); }); it("should work with \\color: \\sqrt[\\color{red} 3]{x}", () => { const tree = stripPositions(getParsed`\sqrt[\color{red} 3]{x}`); expect(tree).toMatchSnapshot(); }); it("should work style commands \\sqrt[\\textstyle 3]{x}", () => { const tree = stripPositions(getParsed`\sqrt[\textstyle 3]{x}`); expect(tree).toMatchSnapshot(); }); it("should work with old font functions: \\sqrt[\\tt 3]{x}", () => { const tree = stripPositions(getParsed`\sqrt[\tt 3]{x}`); expect(tree).toMatchSnapshot(); }); }); }); describe("A function parser", function() { it("should parse no argument functions", function() { expect`\div`.toParse(); }); it("should parse 1 argument functions", function() { expect`\blue x`.toParse(); }); it("should parse 2 argument functions", function() { expect`\frac 1 2`.toParse(); }); it("should not parse 1 argument functions with no arguments", function() { expect`\blue`.not.toParse(); }); it("should not parse 2 argument functions with 0 or 1 arguments", function() { expect`\frac`.not.toParse(); expect`\frac 1`.not.toParse(); }); it("should not parse a function with text right after it", function() { expect`\redx`.not.toParse(); }); it("should parse a function with a number right after it", function() { expect`\frac12`.toParse(); }); it("should parse some functions with text right after it", function() { expect`\;x`.toParse(); }); }); describe("A frac parser", function() { const expression = r`\frac{x}{y}`; const dfracExpression = r`\dfrac{x}{y}`; const tfracExpression = r`\tfrac{x}{y}`; const cfracExpression = r`\cfrac{x}{y}`; const genfrac1 = r`\genfrac ( ] {0.06em}{0}{a}{b+c}`; const genfrac2 = r`\genfrac ( ] {0.8pt}{}{a}{b+c}`; it("should not fail", function() { expect(expression).toParse(); }); it("should produce a frac", function() { const parse = getParsed(expression)[0]; expect(parse.type).toEqual("genfrac"); expect(parse.numer).toBeDefined(); expect(parse.denom).toBeDefined(); }); it("should also parse cfrac, dfrac, tfrac, and genfrac", function() { expect(cfracExpression).toParse(); expect(dfracExpression).toParse(); expect(tfracExpression).toParse(); expect(genfrac1).toParse(); expect(genfrac2).toParse(); }); it("should parse cfrac, dfrac, tfrac, and genfrac as fracs", function() { const dfracParse = getParsed(dfracExpression)[0]; expect(dfracParse.type).toEqual("genfrac"); expect(dfracParse.numer).toBeDefined(); expect(dfracParse.denom).toBeDefined(); const tfracParse = getParsed(tfracExpression)[0]; expect(tfracParse.type).toEqual("genfrac"); expect(tfracParse.numer).toBeDefined(); expect(tfracParse.denom).toBeDefined(); const cfracParse = getParsed(cfracExpression)[0]; expect(cfracParse.type).toEqual("genfrac"); expect(cfracParse.numer).toBeDefined(); expect(cfracParse.denom).toBeDefined(); const genfracParse = getParsed(genfrac1)[0]; expect(genfracParse.type).toEqual("genfrac"); expect(genfracParse.numer).toBeDefined(); expect(genfracParse.denom).toBeDefined(); expect(genfracParse.leftDelim).toBeDefined(); expect(genfracParse.rightDelim).toBeDefined(); }); it("should fail, given math as a line thickness to genfrac", function() { const badGenFrac = "\\genfrac ( ] {b+c}{0}{a}{b+c}"; expect(badGenFrac).not.toParse(); }); it("should fail if genfrac is given less than 6 arguments", function() { const badGenFrac = "\\genfrac ( ] {0.06em}{0}{a}"; expect(badGenFrac).not.toParse(); }); it("should parse atop", function() { const parse = getParsed`x \atop y`[0]; expect(parse.type).toEqual("genfrac"); expect(parse.numer).toBeDefined(); expect(parse.denom).toBeDefined(); expect(parse.hasBarLine).toEqual(false); }); }); describe("An over/brace/brack parser", function() { const simpleOver = r`1 \over x`; const complexOver = r`1+2i \over 3+4i`; const braceFrac = r`a+b \brace c+d`; const brackFrac = r`a+b \brack c+d`; it("should not fail", function() { expect(simpleOver).toParse(); expect(complexOver).toParse(); expect(braceFrac).toParse(); expect(brackFrac).toParse(); }); it("should produce a frac", function() { let parse; parse = getParsed(simpleOver)[0]; expect(parse.type).toEqual("genfrac"); expect(parse.numer).toBeDefined(); expect(parse.denom).toBeDefined(); parse = getParsed(complexOver)[0]; expect(parse.type).toEqual("genfrac"); expect(parse.numer).toBeDefined(); expect(parse.denom).toBeDefined(); const parseBraceFrac = getParsed(braceFrac)[0]; expect(parseBraceFrac.type).toEqual("genfrac"); expect(parseBraceFrac.numer).toBeDefined(); expect(parseBraceFrac.denom).toBeDefined(); expect(parseBraceFrac.leftDelim).toBeDefined(); expect(parseBraceFrac.rightDelim).toBeDefined(); const parseBrackFrac = getParsed(brackFrac)[0]; expect(parseBrackFrac.type).toEqual("genfrac"); expect(parseBrackFrac.numer).toBeDefined(); expect(parseBrackFrac.denom).toBeDefined(); expect(parseBrackFrac.leftDelim).toBeDefined(); expect(parseBrackFrac.rightDelim).toBeDefined(); }); it("should create a numerator from the atoms before \\over", function() { const parse = getParsed(complexOver)[0]; const numer = parse.numer; expect(numer.body).toHaveLength(4); }); it("should create a demonimator from the atoms after \\over", function() { const parse = getParsed(complexOver)[0]; const denom = parse.numer; expect(denom.body).toHaveLength(4); }); it("should handle empty numerators", function() { const emptyNumerator = r`\over x`; const parse = getParsed(emptyNumerator)[0]; expect(parse.type).toEqual("genfrac"); expect(parse.numer).toBeDefined(); expect(parse.denom).toBeDefined(); }); it("should handle empty denominators", function() { const emptyDenominator = r`1 \over`; const parse = getParsed(emptyDenominator)[0]; expect(parse.type).toEqual("genfrac"); expect(parse.numer).toBeDefined(); expect(parse.denom).toBeDefined(); }); it("should handle \\displaystyle correctly", function() { const displaystyleExpression = r`\displaystyle 1 \over 2`; const parse = getParsed(displaystyleExpression)[0]; expect(parse.type).toEqual("genfrac"); expect(parse.numer.body[0].type).toEqual("styling"); expect(parse.denom).toBeDefined(); }); it("should handle \\textstyle correctly", function() { expect`\textstyle 1 \over 2`.toParseLike`\frac{\textstyle 1}{2}`; expect`{\textstyle 1} \over 2`.toParseLike`\frac{\textstyle 1}{2}`; }); it("should handle nested factions", function() { const nestedOverExpression = r`{1 \over 2} \over 3`; const parse = getParsed(nestedOverExpression)[0]; expect(parse.type).toEqual("genfrac"); expect(parse.numer.body[0].type).toEqual("genfrac"); expect(parse.numer.body[0].numer.body[0].text).toEqual("1"); expect(parse.numer.body[0].denom.body[0].text).toEqual("2"); expect(parse.denom).toBeDefined(); expect(parse.denom.body[0].text).toEqual("3"); }); it("should fail with multiple overs in the same group", function() { const badMultipleOvers = r`1 \over 2 + 3 \over 4`; expect(badMultipleOvers).not.toParse(); const badOverChoose = r`1 \over 2 \choose 3`; expect(badOverChoose).not.toParse(); }); }); describe("A genfrac builder", function() { it("should not fail", function() { expect("\\frac{x}{y}").toBuild(); expect("\\dfrac{x}{y}").toBuild(); expect("\\tfrac{x}{y}").toBuild(); expect("\\cfrac{x}{y}").toBuild(); expect("\\genfrac ( ] {0.06em}{0}{a}{b+c}").toBuild(); expect("\\genfrac ( ] {0.8pt}{}{a}{b+c}").toBuild(); expect("\\genfrac {} {} {0.8pt}{}{a}{b+c}").toBuild(); expect("\\genfrac [ {} {0.8pt}{}{a}{b+c}").toBuild(); }); }); describe("A infix builder", function() { it("should not fail", function() { expect("a \\over b").toBuild(); expect("a \\atop b").toBuild(); expect("a \\choose b").toBuild(); expect("a \\brace b").toBuild(); expect("a \\brack b").toBuild(); }); }); describe("A sizing parser", function() { const sizeExpression = r`\Huge{x}\small{x}`; it("should not fail", function() { expect(sizeExpression).toParse(); }); it("should produce a sizing node", function() { const parse = getParsed(sizeExpression)[0]; expect(parse.type).toEqual("sizing"); expect(parse.size).toBeDefined(); expect(parse.body).toBeDefined(); }); }); describe("A text parser", function() { const textExpression = r`\text{a b}`; const noBraceTextExpression = r`\text x`; const nestedTextExpression = r`\text{a {b} \blue{c} \textcolor{#fff}{x} \llap{x}}`; const spaceTextExpression = r`\text{ a \ }`; const leadingSpaceTextExpression = r`\text {moo}`; const badTextExpression = r`\text{a b%}`; const badFunctionExpression = r`\text{\sqrt{x}}`; const mathTokenAfterText = r`\text{sin}^2`; it("should not fail", function() { expect(textExpression).toParse(); }); it("should produce a text", function() { const parse = getParsed(textExpression)[0]; expect(parse.type).toEqual("text"); expect(parse.body).toBeDefined(); }); it("should produce textords instead of mathords", function() { const parse = getParsed(textExpression)[0]; const group = parse.body; expect(group[0].type).toEqual("textord"); }); it("should not parse bad text", function() { expect(badTextExpression).not.toParse(); }); it("should not parse bad functions inside text", function() { expect(badFunctionExpression).not.toParse(); }); it("should parse text with no braces around it", function() { expect(noBraceTextExpression).toParse(); }); it("should parse nested expressions", function() { expect(nestedTextExpression).toParse(); }); it("should contract spaces", function() { const parse = getParsed(spaceTextExpression)[0]; const group = parse.body; expect(group[0].type).toEqual("spacing"); expect(group[1].type).toEqual("textord"); expect(group[2].type).toEqual("spacing"); expect(group[3].type).toEqual("spacing"); }); it("should accept math mode tokens after its argument", function() { expect(mathTokenAfterText).toParse(); }); it("should ignore a space before the text group", function() { const parse = getParsed(leadingSpaceTextExpression)[0]; // [m, o, o] expect(parse.body).toHaveLength(3); expect(parse.body.map(n => n.text).join("")).toBe("moo"); }); it("should parse math within text group", function() { expect`\text{graph: $y = mx + b$}`.toParse(strictSettings); expect`\text{graph: \(y = mx + b\)}`.toParse(strictSettings); }); it("should parse math within text within math within text", function() { expect`\text{hello $x + \text{world $y$} + z$}`.toParse(strictSettings); expect`\text{hello \(x + \text{world $y$} + z\)}`.toParse(strictSettings); expect`\text{hello $x + \text{world \(y\)} + z$}`.toParse(strictSettings); expect`\text{hello \(x + \text{world \(y\)} + z\)}`.toParse(strictSettings); }); it("should forbid \\( within math mode", function() { expect`\(`.not.toParse(); expect`\text{$\(x\)$}`.not.toParse(); }); it("should forbid $ within math mode", function() { expect`$x$`.not.toParse(); expect`\text{\($x$\)}`.not.toParse(); }); it("should detect unbalanced \\)", function() { expect`\)`.not.toParse(); expect`\text{\)}`.not.toParse(); }); it("should detect unbalanced $", function() { expect`$`.not.toParse(); expect`\text{$}`.not.toParse(); }); it("should not mix $ and \\(..\\)", function() { expect`\text{$x\)}`.not.toParse(); expect`\text{\(x$}`.not.toParse(); }); it("should parse spacing functions", function() { expect`a b\, \; \! \: \> ~ \thinspace \medspace \quad \ `.toBuild(); expect`\enspace \thickspace \qquad \space \nobreakspace`.toBuild(); }); it("should omit spaces after commands", function() { expect`\text{\textellipsis !}`.toParseLike`\text{\textellipsis!}`; }); }); describe("A texvc builder", function() { it("should not fail", function() { expect("\\lang\\N\\darr\\R\\dArr\\Z\\Darr\\alef\\rang").toBuild(); expect("\\alefsym\\uarr\\Alpha\\uArr\\Beta\\Uarr\\Chi").toBuild(); expect("\\clubs\\diamonds\\hearts\\spades\\cnums\\Complex").toBuild(); expect("\\Dagger\\empty\\harr\\Epsilon\\hArr\\Eta\\Harr\\exist").toBuild(); expect("\\image\\larr\\infin\\lArr\\Iota\\Larr\\isin\\Kappa").toBuild(); expect("\\Mu\\lrarr\\natnums\\lrArr\\Nu\\Lrarr\\Omicron").toBuild(); expect("\\real\\rarr\\plusmn\\rArr\\reals\\Rarr\\Reals\\Rho").toBuild(); expect("\\text{\\sect}\\sdot\\sub\\sube\\supe").toBuild(); expect("\\Tau\\thetasym\\weierp\\Zeta").toBuild(); }); }); describe("A color parser", function() { const colorExpression = r`\blue{x}`; const newColorExpression = r`\redA{x}`; const customColorExpression1 = r`\textcolor{#fA6}{x}`; const customColorExpression2 = r`\textcolor{#fA6fA6}{x}`; const customColorExpression3 = r`\textcolor{fA6fA6}{x}`; const badCustomColorExpression1 = r`\textcolor{bad-color}{x}`; const badCustomColorExpression2 = r`\textcolor{#fA6f}{x}`; const badCustomColorExpression3 = r`\textcolor{#gA6}{x}`; const oldColorExpression = r`\color{#fA6}xy`; it("should not fail", function() { expect(colorExpression).toParse(); }); it("should build a color node", function() { const parse = getParsed(colorExpression)[0]; expect(parse.type).toEqual("color"); expect(parse.color).toBeDefined(); expect(parse.body).toBeDefined(); }); it("should parse a custom color", function() { expect(customColorExpression1).toParse(); expect(customColorExpression2).toParse(); expect(customColorExpression3).toParse(); }); it("should correctly extract the custom color", function() { const parse1 = getParsed(customColorExpression1)[0]; const parse2 = getParsed(customColorExpression2)[0]; const parse3 = getParsed(customColorExpression3)[0]; expect(parse1.color).toEqual("#fA6"); expect(parse2.color).toEqual("#fA6fA6"); expect(parse3.color).toEqual("#fA6fA6"); }); it("should not parse a bad custom color", function() { expect(badCustomColorExpression1).not.toParse(); expect(badCustomColorExpression2).not.toParse(); expect(badCustomColorExpression3).not.toParse(); }); it("should parse new colors from the branding guide", function() { expect(newColorExpression).toParse(); }); it("should have correct greediness", function() { expect`\textcolor{red}a`.toParse(); expect`\textcolor{red}{\text{a}}`.toParse(); expect`\textcolor{red}\text{a}`.not.toParse(); expect`\textcolor{red}\frac12`.not.toParse(); }); it("should use one-argument \\color by default", function() { expect(oldColorExpression).toParseLike`\textcolor{#fA6}{xy}`; }); it("should use one-argument \\color if requested", function() { expect(oldColorExpression).toParseLike(r`\textcolor{#fA6}{xy}`, { colorIsTextColor: false, }); }); it("should use two-argument \\color if requested", function() { expect(oldColorExpression).toParseLike(r`\textcolor{#fA6}{x}y`, { colorIsTextColor: true, }); }); it("should not define \\color in global context", function() { const macros = {}; expect(oldColorExpression).toParseLike(r`\textcolor{#fA6}{x}y`, { colorIsTextColor: true, macros: macros, }); expect(macros).toEqual({}); }); }); describe("A tie parser", function() { const mathTie = "a~b"; const textTie = r`\text{a~ b}`; it("should parse ties in math mode", function() { expect(mathTie).toParse(); }); it("should parse ties in text mode", function() { expect(textTie).toParse(); }); it("should produce spacing in math mode", function() { const parse = getParsed(mathTie); expect(parse[1].type).toEqual("spacing"); }); it("should produce spacing in text mode", function() { const text = getParsed(textTie)[0]; const parse = text.body; expect(parse[1].type).toEqual("spacing"); }); it("should not contract with spaces in text mode", function() { const text = getParsed(textTie)[0]; const parse = text.body; expect(parse[2].type).toEqual("spacing"); }); }); describe("A delimiter sizing parser", function() { const normalDelim = r`\bigl |`; const notDelim = r`\bigl x`; const bigDelim = r`\Biggr \langle`; it("should parse normal delimiters", function() { expect(normalDelim).toParse(); expect(bigDelim).toParse(); }); it("should not parse not-delimiters", function() { expect(notDelim).not.toParse(); }); it("should produce a delimsizing", function() { const parse = getParsed(normalDelim)[0]; expect(parse.type).toEqual("delimsizing"); }); it("should produce the correct direction delimiter", function() { const leftParse = getParsed(normalDelim)[0]; const rightParse = getParsed(bigDelim)[0]; expect(leftParse.mclass).toEqual("mopen"); expect(rightParse.mclass).toEqual("mclose"); }); it("should parse the correct size delimiter", function() { const smallParse = getParsed(normalDelim)[0]; const bigParse = getParsed(bigDelim)[0]; expect(smallParse.size).toEqual(1); expect(bigParse.size).toEqual(4); }); }); describe("An overline parser", function() { const overline = r`\overline{x}`; it("should not fail", function() { expect(overline).toParse(); }); it("should produce an overline", function() { const parse = getParsed(overline)[0]; expect(parse.type).toEqual("overline"); }); }); describe("An lap parser", function() { it("should not fail on a text argument", function() { expect`\rlap{\,/}{=}`.toParse(); expect`\mathrlap{\,/}{=}`.toParse(); expect`{=}\llap{/\,}`.toParse(); expect`{=}\mathllap{/\,}`.toParse(); expect`\sum_{\clap{ABCDEFG}}`.toParse(); expect`\sum_{\mathclap{ABCDEFG}}`.toParse(); }); it("should not fail if math version is used", function() { expect`\mathrlap{\frac{a}{b}}{=}`.toParse(); expect`{=}\mathllap{\frac{a}{b}}`.toParse(); expect`\sum_{\mathclap{\frac{a}{b}}}`.toParse(); }); it("should fail on math if AMS version is used", function() { expect`\rlap{\frac{a}{b}}{=}`.not.toParse(); expect`{=}\llap{\frac{a}{b}}`.not.toParse(); expect`\sum_{\clap{\frac{a}{b}}}`.not.toParse(); }); it("should produce a lap", function() { const parse = getParsed`\mathrlap{\,/}`[0]; expect(parse.type).toEqual("lap"); }); }); describe("A rule parser", function() { const emRule = r`\rule{1em}{2em}`; const exRule = r`\rule{1ex}{2em}`; const badUnitRule = r`\rule{1au}{2em}`; const noNumberRule = r`\rule{1em}{em}`; const incompleteRule = r`\rule{1em}`; const hardNumberRule = r`\rule{ 01.24ex}{2.450 em }`; it("should not fail", function() { expect(emRule).toParse(); expect(exRule).toParse(); }); it("should not parse invalid units", function() { expect(badUnitRule).not.toParse(); expect(noNumberRule).not.toParse(); }); it("should not parse incomplete rules", function() { expect(incompleteRule).not.toParse(); }); it("should produce a rule", function() { const parse = getParsed(emRule)[0]; expect(parse.type).toEqual("rule"); }); it("should list the correct units", function() { const emParse = getParsed(emRule)[0]; const exParse = getParsed(exRule)[0]; expect(emParse.width.unit).toEqual("em"); expect(emParse.height.unit).toEqual("em"); expect(exParse.width.unit).toEqual("ex"); expect(exParse.height.unit).toEqual("em"); }); it("should parse the number correctly", function() { const hardNumberParse = getParsed(hardNumberRule)[0]; expect(hardNumberParse.width.number).toBeCloseTo(1.24); expect(hardNumberParse.height.number).toBeCloseTo(2.45); }); it("should parse negative sizes", function() { const parse = getParsed`\rule{-1em}{- 0.2em}`[0]; expect(parse.width.number).toBeCloseTo(-1); expect(parse.height.number).toBeCloseTo(-0.2); }); }); describe("A kern parser", function() { const emKern = r`\kern{1em}`; const exKern = r`\kern{1ex}`; const muKern = r`\mkern{1mu}`; const abKern = r`a\kern{1em}b`; const badUnitRule = r`\kern{1au}`; const noNumberRule = r`\kern{em}`; it("should list the correct units", function() { const emParse = getParsed(emKern)[0]; const exParse = getParsed(exKern)[0]; const muParse = getParsed(muKern)[0]; const abParse = getParsed(abKern)[1]; expect(emParse.dimension.unit).toEqual("em"); expect(exParse.dimension.unit).toEqual("ex"); expect(muParse.dimension.unit).toEqual("mu"); expect(abParse.dimension.unit).toEqual("em"); }); it("should not parse invalid units", function() { expect(badUnitRule).not.toParse(); expect(noNumberRule).not.toParse(); }); it("should parse negative sizes", function() { const parse = getParsed`\kern{-1em}`[0]; expect(parse.dimension.number).toBeCloseTo(-1); }); it("should parse positive sizes", function() { const parse = getParsed`\kern{+1em}`[0]; expect(parse.dimension.number).toBeCloseTo(1); }); }); describe("A non-braced kern parser", function() { const emKern = r`\kern1em`; const exKern = r`\kern 1 ex`; const muKern = r`\mkern 1mu`; const abKern1 = r`a\mkern1mub`; const abKern2 = r`a\mkern-1mub`; const abKern3 = r`a\mkern-1mu b`; const badUnitRule = r`\kern1au`; const noNumberRule = r`\kern em`; it("should list the correct units", function() { const emParse = getParsed(emKern)[0]; const exParse = getParsed(exKern)[0]; const muParse = getParsed(muKern)[0]; const abParse1 = getParsed(abKern1)[1]; const abParse2 = getParsed(abKern2)[1]; const abParse3 = getParsed(abKern3)[1]; expect(emParse.dimension.unit).toEqual("em"); expect(exParse.dimension.unit).toEqual("ex"); expect(muParse.dimension.unit).toEqual("mu"); expect(abParse1.dimension.unit).toEqual("mu"); expect(abParse2.dimension.unit).toEqual("mu"); expect(abParse3.dimension.unit).toEqual("mu"); }); it("should parse elements on either side of a kern", function() { const abParse1 = getParsed(abKern1); const abParse2 = getParsed(abKern2); const abParse3 = getParsed(abKern3); expect(abParse1).toHaveLength(3); expect(abParse1[0].text).toEqual("a"); expect(abParse1[2].text).toEqual("b"); expect(abParse2).toHaveLength(3); expect(abParse2[0].text).toEqual("a"); expect(abParse2[2].text).toEqual("b"); expect(abParse3).toHaveLength(3); expect(abParse3[0].text).toEqual("a"); expect(abParse3[2].text).toEqual("b"); }); it("should not parse invalid units", function() { expect(badUnitRule).not.toParse(); expect(noNumberRule).not.toParse(); }); it("should parse negative sizes", function() { const parse = getParsed`\kern-1em`[0]; expect(parse.dimension.number).toBeCloseTo(-1); }); it("should parse positive sizes", function() { const parse = getParsed`\kern+1em`[0]; expect(parse.dimension.number).toBeCloseTo(1); }); it("should handle whitespace", function() { const abKern = "a\\mkern\t-\r1 \n mu\nb"; const abParse = getParsed(abKern); expect(abParse).toHaveLength(3); expect(abParse[0].text).toEqual("a"); expect(abParse[1].dimension.unit).toEqual("mu"); expect(abParse[2].text).toEqual("b"); }); }); describe("A left/right parser", function() { const normalLeftRight = r`\left( \dfrac{x}{y} \right)`; const emptyRight = r`\left( \dfrac{x}{y} \right.`; it("should not fail", function() { expect(normalLeftRight).toParse(); }); it("should produce a leftright", function() { const parse = getParsed(normalLeftRight)[0]; expect(parse.type).toEqual("leftright"); expect(parse.left).toEqual("("); expect(parse.right).toEqual(")"); }); it("should error when it is mismatched", function() { const unmatchedLeft = r`\left( \dfrac{x}{y}`; const unmatchedRight = r`\dfrac{x}{y} \right)`; expect(unmatchedLeft).not.toParse(); expect(unmatchedRight).not.toParse(); }); it("should error when braces are mismatched", function() { const unmatched = r`{ \left( \dfrac{x}{y} } \right)`; expect(unmatched).not.toParse(); }); it("should error when non-delimiters are provided", function() { const nonDelimiter = r`\left$ \dfrac{x}{y} \right)`; expect(nonDelimiter).not.toParse(); }); it("should parse the empty '.' delimiter", function() { expect(emptyRight).toParse(); }); it("should parse the '.' delimiter with normal sizes", function() { const normalEmpty = r`\Bigl .`; expect(normalEmpty).toParse(); }); it("should handle \\middle", function() { const normalMiddle = r`\left( \dfrac{x}{y} \middle| \dfrac{y}{z} \right)`; expect(normalMiddle).toParse(); }); it("should handle multiple \\middles", function() { const multiMiddle = r`\left( \dfrac{x}{y} \middle| \dfrac{y}{z} \middle/ \dfrac{z}{q} \right)`; expect(multiMiddle).toParse(); }); it("should handle nested \\middles", function() { const nestedMiddle = r`\left( a^2 \middle| \left( b \middle/ c \right) \right)`; expect(nestedMiddle).toParse(); }); it("should error when \\middle is not in \\left...\\right", function() { const unmatchedMiddle = r`(\middle|\dfrac{x}{y})`; expect(unmatchedMiddle).not.toParse(); }); }); describe("left/right builder", () => { const cases = [ [r`\left\langle \right\rangle`, r`\left< \right>`], [r`\left\langle \right\rangle`, '\\left\u27e8 \\right\u27e9'], [r`\left\lparen \right\rparen`, r`\left( \right)`], ]; for (const [actual, expected] of cases) { it(`should build "${actual}" like "${expected}"`, () => { expect(actual).toBuildLike(expected); }); } }); describe("A begin/end parser", function() { it("should parse a simple environment", function() { expect`\begin{matrix}a&b\\c&d\end{matrix}`.toParse(); }); it("should parse an environment with argument", function() { expect`\begin{array}{cc}a&b\\c&d\end{array}`.toParse(); }); it("should parse an environment with hlines", function() { expect`\begin{matrix}\hline a&b\\ \hline c&d\end{matrix}`.toParse(); expect`\begin{matrix}\hdashline a&b\\ \hdashline c&d\end{matrix}`.toParse(); }); it("should forbid hlines outside array environment", () => { expect`\hline`.not.toParse(); }); it("should error when name is mismatched", function() { expect`\begin{matrix}a&b\\c&d\end{pmatrix}`.not.toParse(); }); it("should error when commands are mismatched", function() { expect`\begin{matrix}a&b\\c&d\right{pmatrix}`.not.toParse(); }); it("should error when end is missing", function() { expect`\begin{matrix}a&b\\c&d`.not.toParse(); }); it("should error when braces are mismatched", function() { expect`{\begin{matrix}a&b\\c&d}\end{matrix}`.not.toParse(); }); it("should cooperate with infix notation", function() { expect`\begin{matrix}0&1\over2&3\\4&5&6\end{matrix}`.toParse(); }); it("should nest", function() { const m1 = r`\begin{pmatrix}1&2\\3&4\end{pmatrix}`; const m2 = `\\begin{array}{rl}${m1}&0\\\\0&${m1}\\end{array}`; expect(m2).toParse(); }); it("should allow \\cr as a line terminator", function() { expect`\begin{matrix}a&b\cr c&d\end{matrix}`.toParse(); }); it("should eat a final newline", function() { const m3 = getParsed`\begin{matrix}a&b\\ c&d \\ \end{matrix}`[0]; expect(m3.body).toHaveLength(2); }); it("should grab \\arraystretch", function() { const parse = getParsed`\def\arraystretch{1.5}\begin{matrix}a&b\\c&d\end{matrix}`; expect(parse).toMatchSnapshot(); }); }); describe("A sqrt parser", function() { const sqrt = r`\sqrt{x}`; const missingGroup = r`\sqrt`; it("should parse square roots", function() { expect(sqrt).toParse(); }); it("should error when there is no group", function() { expect(missingGroup).not.toParse(); }); it("should produce sqrts", function() { const parse = getParsed(sqrt)[0]; expect(parse.type).toEqual("sqrt"); }); it("should build sized square roots", function() { expect("\\Large\\sqrt[3]{x}").toBuild(); }); }); describe("A TeX-compliant parser", function() { it("should work", function() { expect`\frac 2 3`.toParse(); }); it("should fail if there are not enough arguments", function() { const missingGroups = [ r`\frac{x}`, r`\textcolor{#fff}`, r`\rule{1em}`, r`\llap`, r`\bigl`, r`\text`, ]; for (let i = 0; i < missingGroups.length; i++) { expect(missingGroups[i]).not.toParse(); } }); it("should fail when there are missing sup/subscripts", function() { expect`x^`.not.toParse(); expect`x_`.not.toParse(); }); it("should fail when arguments require arguments", function() { const badArguments = [ r`\frac \frac x y z`, r`\frac x \frac y z`, r`\frac \sqrt x y`, r`\frac x \sqrt y`, r`\frac \mathllap x y`, r`\frac x \mathllap y`, // This actually doesn't work in real TeX, but it is suprisingly // hard to get this to correctly work. So, we take hit of very small // amounts of non-compatiblity in order for the rest of the tests to // work // r`\llap \frac x y`, r`\mathllap \mathllap x`, r`\sqrt \mathllap x`, ]; for (let i = 0; i < badArguments.length; i++) { expect(badArguments[i]).not.toParse(); } }); it("should work when the arguments have braces", function() { const goodArguments = [ r`\frac {\frac x y} z`, r`\frac x {\frac y z}`, r`\frac {\sqrt x} y`, r`\frac x {\sqrt y}`, r`\frac {\mathllap x} y`, r`\frac x {\mathllap y}`, r`\mathllap {\frac x y}`, r`\mathllap {\mathllap x}`, r`\sqrt {\mathllap x}`, ]; for (let i = 0; i < goodArguments.length; i++) { expect(goodArguments[i]).toParse(); } }); it("should fail when sup/subscripts require arguments", function() { const badSupSubscripts = [ r`x^\sqrt x`, r`x^\mathllap x`, r`x_\sqrt x`, r`x_\mathllap x`, ]; for (let i = 0; i < badSupSubscripts.length; i++) { expect(badSupSubscripts[i]).not.toParse(); } }); it("should work when sup/subscripts arguments have braces", function() { const goodSupSubscripts = [ r`x^{\sqrt x}`, r`x^{\mathllap x}`, r`x_{\sqrt x}`, r`x_{\mathllap x}`, ]; for (let i = 0; i < goodSupSubscripts.length; i++) { expect(goodSupSubscripts[i]).toParse(); } }); it("should parse multiple primes correctly", function() { expect`x''''`.toParse(); expect`x_2''`.toParse(); expect`x''_2`.toParse(); }); it("should fail when sup/subscripts are interspersed with arguments", function() { expect`\sqrt^23`.not.toParse(); expect`\frac^234`.not.toParse(); expect`\frac2^34`.not.toParse(); }); it("should succeed when sup/subscripts come after whole functions", function() { expect`\sqrt2^3`.toParse(); expect`\frac23^4`.toParse(); }); it("should succeed with a sqrt around a text/frac", function() { expect`\sqrt \frac x y`.toParse(); expect`\sqrt \text x`.toParse(); expect`x^\frac x y`.toParse(); expect`x_\text x`.toParse(); }); it("should fail when arguments are \\left", function() { const badLeftArguments = [ r`\frac \left( x \right) y`, r`\frac x \left( y \right)`, r`\mathllap \left( x \right)`, r`\sqrt \left( x \right)`, r`x^\left( x \right)`, ]; for (let i = 0; i < badLeftArguments.length; i++) { expect(badLeftArguments[i]).not.toParse(); } }); it("should succeed when there are braces around the \\left/\\right", function() { const goodLeftArguments = [ r`\frac {\left( x \right)} y`, r`\frac x {\left( y \right)}`, r`\mathllap {\left( x \right)}`, r`\sqrt {\left( x \right)}`, r`x^{\left( x \right)}`, ]; for (let i = 0; i < goodLeftArguments.length; i++) { expect(goodLeftArguments[i]).toParse(); } }); }); describe("An op symbol builder", function() { it("should not fail", function() { expect("\\int_i^n").toBuild(); expect("\\iint_i^n").toBuild(); expect("\\iiint_i^n").toBuild(); expect("\\int\nolimits_i^n").toBuild(); expect("\\iint\nolimits_i^n").toBuild(); expect("\\iiint\nolimits_i^n").toBuild(); expect("\\oint_i^n").toBuild(); expect("\\oiint_i^n").toBuild(); expect("\\oiiint_i^n").toBuild(); expect("\\oint\nolimits_i^n").toBuild(); expect("\\oiint\nolimits_i^n").toBuild(); expect("\\oiiint\nolimits_i^n").toBuild(); }); }); describe("A style change parser", function() { it("should not fail", function() { expect`\displaystyle x`.toParse(); expect`\textstyle x`.toParse(); expect`\scriptstyle x`.toParse(); expect`\scriptscriptstyle x`.toParse(); }); it("should produce the correct style", function() { const displayParse = getParsed`\displaystyle x`[0]; expect(displayParse.style).toEqual("display"); const scriptscriptParse = getParsed`\scriptscriptstyle x`[0]; expect(scriptscriptParse.style).toEqual("scriptscript"); }); it("should only change the style within its group", function() { const text = r`a b { c d \displaystyle e f } g h`; const parse = getParsed(text); const displayNode = parse[2].body[2]; expect(displayNode.type).toEqual("styling"); const displayBody = displayNode.body; expect(displayBody).toHaveLength(2); expect(displayBody[0].text).toEqual("e"); }); }); describe("A font parser", function() { it("should parse \\mathrm, \\mathbb, \\mathit, and \\mathnormal", function() { expect`\mathrm x`.toParse(); expect`\mathbb x`.toParse(); expect`\mathit x`.toParse(); expect`\mathnormal x`.toParse(); expect`\mathrm {x + 1}`.toParse(); expect`\mathbb {x + 1}`.toParse(); expect`\mathit {x + 1}`.toParse(); expect`\mathnormal {x + 1}`.toParse(); }); it("should parse \\mathcal and \\mathfrak", function() { expect`\mathcal{ABC123}`.toParse(); expect`\mathfrak{abcABC123}`.toParse(); }); it("should produce the correct fonts", function() { const mathbbParse = getParsed`\mathbb x`[0]; expect(mathbbParse.font).toEqual("mathbb"); expect(mathbbParse.type).toEqual("font"); const mathrmParse = getParsed`\mathrm x`[0]; expect(mathrmParse.font).toEqual("mathrm"); expect(mathrmParse.type).toEqual("font"); const mathitParse = getParsed`\mathit x`[0]; expect(mathitParse.font).toEqual("mathit"); expect(mathitParse.type).toEqual("font"); const mathnormalParse = getParsed`\mathnormal x`[0]; expect(mathnormalParse.font).toEqual("mathnormal"); expect(mathnormalParse.type).toEqual("font"); const mathcalParse = getParsed`\mathcal C`[0]; expect(mathcalParse.font).toEqual("mathcal"); expect(mathcalParse.type).toEqual("font"); const mathfrakParse = getParsed`\mathfrak C`[0]; expect(mathfrakParse.font).toEqual("mathfrak"); expect(mathfrakParse.type).toEqual("font"); }); it("should parse nested font commands", function() { const nestedParse = getParsed`\mathbb{R \neq \mathrm{R}}`[0]; expect(nestedParse.font).toEqual("mathbb"); expect(nestedParse.type).toEqual("font"); const bbBody = nestedParse.body.body; expect(bbBody).toHaveLength(3); expect(bbBody[0].type).toEqual("mathord"); expect(bbBody[2].type).toEqual("font"); expect(bbBody[2].font).toEqual("mathrm"); expect(bbBody[2].type).toEqual("font"); }); it("should work with \\textcolor", function() { const colorMathbbParse = getParsed`\textcolor{blue}{\mathbb R}`[0]; expect(colorMathbbParse.type).toEqual("color"); expect(colorMathbbParse.color).toEqual("blue"); const body = colorMathbbParse.body; expect(body).toHaveLength(1); expect(body[0].type).toEqual("font"); expect(body[0].font).toEqual("mathbb"); }); it("should not parse a series of font commands", function() { expect`\mathbb \mathrm R`.not.toParse(); }); it("should nest fonts correctly", function() { const bf = getParsed`\mathbf{a\mathrm{b}c}`[0]; expect(bf.type).toEqual("font"); expect(bf.font).toEqual("mathbf"); expect(bf.body.body).toHaveLength(3); expect(bf.body.body[0].text).toEqual("a"); expect(bf.body.body[1].type).toEqual("font"); expect(bf.body.body[1].font).toEqual("mathrm"); expect(bf.body.body[2].text).toEqual("c"); }); it("should have the correct greediness", function() { expect`e^\mathbf{x}`.toParse(); }); it("\\boldsymbol should inherit mbin/mrel from argument", () => { const built = getBuilt`a\boldsymbol{}b\boldsymbol{=}c\boldsymbol{+}d\boldsymbol{++}e\boldsymbol{xyz}f`; expect(built).toMatchSnapshot(); }); }); describe("A \\pmb builder", function() { it("should not fail", function() { expect("\\pmb{\\mu}").toBuild(); expect("\\pmb{=}").toBuild(); expect("\\pmb{+}").toBuild(); expect("\\pmb{\\frac{x^2}{x_1}}").toBuild(); expect("\\pmb{}").toBuild(); expect("\\def\\x{1}\\pmb{\\x\\def\\x{2}}").toParseLike("\\pmb{1}"); }); }); describe("A comment parser", function() { it("should parse comments at the end of a line", () => { expect("a^2 + b^2 = c^2 % Pythagoras' Theorem\n").toParse(); }); it("should parse comments at the start of a line", () => { expect("% comment\n").toParse(); }); it("should parse multiple lines of comments in a row", () => { expect("% comment 1\n% comment 2\n").toParse(); }); it("should parse comments between subscript and superscript", () => { expect("x_3 %comment\n^2").toParseLike`x_3^2`; expect("x^ %comment\n{2}").toParseLike`x^{2}`; expect("x^ %comment\n\\frac{1}{2}").toParseLike`x^\frac{1}{2}`; }); it("should parse comments in size and color groups", () => { expect("\\kern{1 %kern\nem}").toParse(); expect("\\kern1 %kern\nem").toParse(); expect("\\color{#f00%red\n}").toParse(); }); it("should parse comments before an expression", () => { expect("%comment\n{2}").toParseLike`{2}`; }); it("should parse comments before and between \\hline", () => { expect("\\begin{matrix}a&b\\\\ %hline\n" + "\\hline %hline\n" + "\\hline c&d\\end{matrix}").toParse(); }); it("should parse comments in the macro definition", () => { expect("\\def\\foo{1 %}\n2}\n\\foo").toParseLike`12`; }); it("should not expand nor ignore spaces after a command sequence in a comment", () => { expect("\\def\\foo{1\n2}\nx %\\foo\n").toParseLike`x`; }); it("should not parse a comment without newline in strict mode", () => { expect`x%y`.not.toParse(strictSettings); expect`x%y`.toParse(nonstrictSettings); }); it("should not produce or consume space", () => { expect("\\text{hello% comment 1\nworld}").toParseLike`\text{helloworld}`; expect("\\text{hello% comment\n\nworld}").toParseLike`\text{hello world}`; }); it("should not include comments in the output", () => { expect("5 % comment\n").toParseLike`5`; }); }); describe("An HTML font tree-builder", function() { it("should render \\mathbb{R} with the correct font", function() { const markup = katex.renderToString(r`\mathbb{R}`); expect(markup).toContain("R"); }); it("should render \\mathrm{R} with the correct font", function() { const markup = katex.renderToString(r`\mathrm{R}`); expect(markup).toContain("R"); }); it("should render \\mathcal{R} with the correct font", function() { const markup = katex.renderToString(r`\mathcal{R}`); expect(markup).toContain("R"); }); it("should render \\mathfrak{R} with the correct font", function() { const markup = katex.renderToString(r`\mathfrak{R}`); expect(markup).toContain("R"); }); it("should render \\text{R} with the correct font", function() { const markup = katex.renderToString(r`\text{R}`); expect(markup).toContain("R"); }); it("should render \\textit{R} with the correct font", function() { const markup = katex.renderToString(r`\textit{R}`); expect(markup).toContain("R"); }); it("should render \\text{\\textit{R}} with the correct font", function() { const markup = katex.renderToString(r`\text{\textit{R}}`); expect(markup).toContain("R"); }); it("should render \\textup{R} with the correct font", function() { const markup1 = katex.renderToString(r`\textup{R}`); expect(markup1).toContain("R"); const markup2 = katex.renderToString(r`\textit{\textup{R}}`); expect(markup2).toContain("R"); const markup3 = katex.renderToString(r`\textup{\textit{R}}`); expect(markup3).toContain("R"); }); it("should render \\text{R\\textit{S}T} with the correct fonts", function() { const markup = katex.renderToString(r`\text{R\textit{S}T}`); expect(markup).toContain("R"); expect(markup).toContain("S"); expect(markup).toContain("T"); }); it("should render \\textbf{R} with the correct font", function() { const markup = katex.renderToString(r`\textbf{R}`); expect(markup).toContain("R"); }); it("should render \\textmd{R} with the correct font", function() { const markup1 = katex.renderToString(r`\textmd{R}`); expect(markup1).toContain("R"); const markup2 = katex.renderToString(r`\textbf{\textmd{R}}`); expect(markup2).toContain("R"); const markup3 = katex.renderToString(r`\textmd{\textbf{R}}`); expect(markup3).toContain("R"); }); it("should render \\textsf{R} with the correct font", function() { const markup = katex.renderToString(r`\textsf{R}`); expect(markup).toContain("R"); }); it("should render \\textsf{\\textit{R}G\\textbf{B}} with the correct font", function() { const markup = katex.renderToString(r`\textsf{\textit{R}G\textbf{B}}`); expect(markup).toContain("R"); expect(markup).toContain("G"); expect(markup).toContain("B"); }); it("should render \\textsf{\\textbf{$\\mathrm{A}$}} with the correct font", function() { const markup = katex.renderToString(r`\textsf{\textbf{$\mathrm{A}$}}`); expect(markup).toContain("A"); }); it("should render \\textsf{\\textbf{$\\mathrm{\\textsf{A}}$}} with the correct font", function() { const markup = katex.renderToString(r`\textsf{\textbf{$\mathrm{\textsf{A}}$}}`); expect(markup).toContain("A"); }); it("should render \\texttt{R} with the correct font", function() { const markup = katex.renderToString(r`\texttt{R}`); expect(markup).toContain("R"); }); it("should render a combination of font and color changes", function() { let markup = katex.renderToString(r`\textcolor{blue}{\mathbb R}`); let span = "R"; expect(markup).toContain(span); markup = katex.renderToString(r`\mathbb{\textcolor{blue}{R}}`); span = "R"; expect(markup).toContain(span); }); it("should render wide characters with mord and with the correct font", function() { const markup = katex.renderToString(String.fromCharCode(0xD835, 0xDC00)); expect(markup).toContain("A"); expect(String.fromCharCode(0xD835, 0xDC00) + " = " + String.fromCharCode(0xD835, 0xDC1A)) .toBuildLike`\mathbf A = \mathbf a`; }); it("should throw TypeError when the expression is of the wrong type", function() { expect(function() { katex.renderToString({badInputType: "yes"}); }).toThrowError(TypeError); expect(function() { katex.renderToString([1, 2]); }).toThrowError(TypeError); expect(function() { katex.renderToString(undefined); }).toThrowError(TypeError); expect(function() { katex.renderToString(null); }).toThrowError(TypeError); expect(function() { katex.renderToString(1.234); }).toThrowError(TypeError); }); it("should not throw TypeError when the expression is a supported type", function() { expect(function() { katex.renderToString(r`\sqrt{123}`); }).not.toThrowError(TypeError); expect(function() { katex.renderToString(new String(r`\sqrt{123}`)); }).not.toThrowError(TypeError); }); }); describe("A MathML font tree-builder", function() { const contents = r`Ax2k\omega\Omega\imath+`; it("should render " + contents + " with the correct mathvariants", function() { const tree = getParsed(contents); const markup = buildMathML(tree, contents, defaultOptions).toMarkup(); expect(markup).toContain("A"); expect(markup).toContain("x"); expect(markup).toContain("2"); expect(markup).toContain("\u03c9"); // \omega expect(markup).toContain("\u03A9"); // \Omega expect(markup).toContain("\u0131"); // \imath expect(markup).toContain("+"); }); it("should render \\mathbb{" + contents + "} with the correct mathvariants", function() { const tex = `\\mathbb{${contents}}`; const tree = getParsed(tex); const markup = buildMathML(tree, tex, defaultOptions).toMarkup(); expect(markup).toContain("A"); expect(markup).toContain("x"); expect(markup).toContain("2"); expect(markup).toContain("\u03c9"); // \omega expect(markup).toContain("\u03A9"); // \Omega expect(markup).toContain("\u0131"); // \imath expect(markup).toContain("+"); }); it("should render \\mathrm{" + contents + "} with the correct mathvariants", function() { const tex = `\\mathrm{${contents}}`; const tree = getParsed(tex); const markup = buildMathML(tree, tex, defaultOptions).toMarkup(); expect(markup).toContain("A"); expect(markup).toContain("x"); expect(markup).toContain("2"); expect(markup).toContain("\u03c9"); // \omega expect(markup).toContain("\u03A9"); // \Omega expect(markup).toContain("\u0131"); // \imath expect(markup).toContain("+"); }); it("should render \\mathit{" + contents + "} with the correct mathvariants", function() { const tex = `\\mathit{${contents}}`; const tree = getParsed(tex); const markup = buildMathML(tree, tex, defaultOptions).toMarkup(); expect(markup).toContain("A"); expect(markup).toContain("x"); expect(markup).toContain("2"); expect(markup).toContain("\u03c9"); // \omega expect(markup).toContain("\u03A9"); // \Omega expect(markup).toContain("\u0131"); // \imath expect(markup).toContain("+"); }); it("should render \\mathnormal{" + contents + "} with the correct mathvariants", function() { const tex = `\\mathnormal{${contents}}`; const tree = getParsed(tex); const markup = buildMathML(tree, tex, defaultOptions).toMarkup(); expect(markup).toContain("A"); expect(markup).toContain("x"); expect(markup).toContain("2"); expect(markup).toContain("\u03c9"); // \omega expect(markup).toContain("\u03A9"); // \Omega expect(markup).toContain("\u0131"); // \imath expect(markup).toContain("+"); }); it("should render \\mathbf{" + contents + "} with the correct mathvariants", function() { const tex = `\\mathbf{${contents}}`; const tree = getParsed(tex); const markup = buildMathML(tree, tex, defaultOptions).toMarkup(); expect(markup).toContain("A"); expect(markup).toContain("x"); expect(markup).toContain("2"); expect(markup).toContain("\u03c9"); // \omega expect(markup).toContain("\u03A9"); // \Omega expect(markup).toContain("\u0131"); // \imath expect(markup).toContain("+"); }); it("should render \\mathcal{" + contents + "} with the correct mathvariants", function() { const tex = `\\mathcal{${contents}}`; const tree = getParsed(tex); const markup = buildMathML(tree, tex, defaultOptions).toMarkup(); expect(markup).toContain("A"); expect(markup).toContain("x"); expect(markup).toContain("2"); expect(markup).toContain("\u03c9"); // \omega expect(markup).toContain("\u03A9"); // \Omega expect(markup).toContain("\u0131"); // \imath expect(markup).toContain("+"); }); it("should render \\mathfrak{" + contents + "} with the correct mathvariants", function() { const tex = `\\mathfrak{${contents}}`; const tree = getParsed(tex); const markup = buildMathML(tree, tex, defaultOptions).toMarkup(); expect(markup).toContain("A"); expect(markup).toContain("x"); expect(markup).toContain("2"); expect(markup).toContain("\u03c9"); // \omega expect(markup).toContain("\u03A9"); // \Omega expect(markup).toContain("\u0131"); // \imath expect(markup).toContain("+"); }); it("should render \\mathscr{" + contents + "} with the correct mathvariants", function() { const tex = `\\mathscr{${contents}}`; const tree = getParsed(tex); const markup = buildMathML(tree, tex, defaultOptions).toMarkup(); expect(markup).toContain("A"); expect(markup).toContain("x"); expect(markup).toContain("2"); expect(markup).toContain("\u03c9"); // \omega expect(markup).toContain("\u03A9"); // \Omega expect(markup).toContain("\u0131"); // \imath expect(markup).toContain("+"); }); it("should render \\mathsf{" + contents + "} with the correct mathvariants", function() { const tex = `\\mathsf{${contents}}`; const tree = getParsed(tex); const markup = buildMathML(tree, tex, defaultOptions).toMarkup(); expect(markup).toContain("A"); expect(markup).toContain("x"); expect(markup).toContain("2"); expect(markup).toContain("\u03c9"); // \omega expect(markup).toContain("\u03A9"); // \Omega expect(markup).toContain("\u0131"); // \imath expect(markup).toContain("+"); }); it("should render a combination of font and color changes", function() { let tex = r`\textcolor{blue}{\mathbb R}`; let tree = getParsed(tex); let markup = buildMathML(tree, tex, defaultOptions).toMarkup(); let node = "" + "R" + ""; expect(markup).toContain(node); // reverse the order of the commands tex = r`\mathbb{\textcolor{blue}{R}}`; tree = getParsed(tex); markup = buildMathML(tree, tex, defaultOptions).toMarkup(); node = "" + "R" + ""; expect(markup).toContain(node); }); it("should render text as ", function() { const tex = r`\text{for }`; const tree = getParsed(tex); const markup = buildMathML(tree, tex, defaultOptions).toMarkup(); expect(markup).toContain("for\u00a0"); }); it("should render math within text as side-by-side children", function() { const tex = r`\text{graph: $y = mx + b$}`; const tree = getParsed(tex); const markup = buildMathML(tree, tex, defaultOptions).toMarkup(); expect(markup).toContain("graph:\u00a0"); expect(markup).toContain( "y=mx+b"); }); }); // Disabled until https://github.com/KaTeX/KaTeX/pull/1794 is merged. describe.skip("An includegraphics builder", function() { const img = "\\includegraphics[height=0.9em, totalheight=0.9em, width=0.9em, alt=KA logo]{https://cdn.kastatic.org/images/apple-touch-icon-57x57-precomposed.new.png}"; it("should not fail", function() { expect(img).toBuild(); }); it("should produce mords", function() { expect(getBuilt(img)[0].classes).toContain("mord"); }); }); describe("A bin builder", function() { it("should create mbins normally", function() { const built = getBuilt`x + y`; // we add glue elements around the '+' expect(built[2].classes).toContain("mbin"); }); it("should create ords when at the beginning of lists", function() { const built = getBuilt`+ x`; expect(built[0].classes).toContain("mord"); expect(built[0].classes).not.toContain("mbin"); }); it("should create ords after some other objects", function() { expect(getBuilt`x + + 2`[4].classes).toContain("mord"); expect(getBuilt`( + 2`[2].classes).toContain("mord"); expect(getBuilt`= + 2`[2].classes).toContain("mord"); expect(getBuilt`\sin + 2`[2].classes).toContain("mord"); expect(getBuilt`, + 2`[2].classes).toContain("mord"); }); it("should correctly interact with color objects", function() { expect(getBuilt`\blue{x}+y`[2].classes).toContain("mbin"); expect(getBuilt`\blue{x+}+y`[2].classes).toContain("mbin"); expect(getBuilt`\blue{x+}+y`[4].classes).toContain("mord"); }); }); describe("A \\phantom builder and \\smash builder", function() { it("should both build a mord", function() { expect(getBuilt`\hphantom{a}`[0].classes).toContain("mord"); expect(getBuilt`a\hphantom{=}b`[2].classes).toContain("mord"); expect(getBuilt`a\hphantom{+}b`[2].classes).toContain("mord"); expect(getBuilt`\smash{a}`[0].classes).toContain("mord"); expect(getBuilt`\smash{=}`[0].classes).toContain("mord"); expect(getBuilt`a\smash{+}b`[2].classes).toContain("mord"); }); }); describe("A markup generator", function() { it("marks trees up", function() { // Just a few quick sanity checks here... const markup = katex.renderToString(r`\sigma^2`); expect(markup.indexOf(" { expect`\href%end`.toParseLike("\\href{%}end"); expect("\\url%end").toParseLike("\\url{%}end"); expect("\\url%%end\n").toParseLike("\\url{%}"); expect("\\url end").toParseLike("\\url{e}nd"); expect("\\url%end").toParseLike("\\url {%}end"); }); it("should allow spaces single-character URLs", () => { expect`\href %end`.toParseLike("\\href{%}end"); expect("\\url %end").toParseLike("\\url{%}end"); }); it("should allow letters [#$%&~_^] without escaping", function() { const url = "http://example.org/~bar/#top?foo=$foo&bar=ba^r_boo%20baz"; const parsed1 = getParsed(`\\href{${url}}{\\alpha}`)[0]; expect(parsed1.href).toBe(url); const parsed2 = getParsed(`\\url{${url}}`)[0]; expect(parsed2.href).toBe(url); }); it("should allow balanced braces in url", function() { const url = "http://example.org/{{}t{oo}}"; const parsed1 = getParsed(`\\href{${url}}{\\alpha}`)[0]; expect(parsed1.href).toBe(url); const parsed2 = getParsed(`\\url{${url}}`)[0]; expect(parsed2.href).toBe(url); }); it("should not allow unbalanced brace(s) in url", function() { expect`\href{http://example.com/{a}{bar}`.not.toParse(); expect`\href{http://example.com/}a}{bar}`.not.toParse(); expect`\\url{http://example.com/{a}`.not.toParse(); expect`\\url{http://example.com/}a}`.not.toParse(); }); it("should allow escape for letters [#$%&~_^{}]", function() { const url = "http://example.org/~bar/#top?foo=$}foo{&bar=bar^r_boo%20baz"; const input = url.replace(/([#$%&~_^{}])/g, '\\$1'); const parsed1 = getParsed(`\\href{${input}}{\\alpha}`)[0]; expect(parsed1.href).toBe(url); const parsed2 = getParsed(`\\url{${input}}`)[0]; expect(parsed2.href).toBe(url); }); it("should allow comments after URLs", function() { expect("\\url{http://example.com/}%comment\n").toBuild(); }); it("should be marked up correctly", function() { const markup = katex.renderToString(r`\href{http://example.com/}{example here}`); expect(markup).toContain(""); }); it("should allow protocols in allowedProtocols", function() { expect("\\href{relative}{foo}").toParse(); expect("\\href{ftp://x}{foo}").toParse(new Settings({ allowedProtocols: ["ftp"], })); expect("\\href{ftp://x}{foo}").toParse(new Settings({ allowedProtocols: ["*"], })); }); it("should not allow protocols not in allowedProtocols", function() { expect("\\href{javascript:alert('x')}{foo}").not.toParse(); expect("\\href{relative}{foo}").not.toParse(new Settings({ allowedProtocols: [], })); }); it("should not affect spacing around", function() { const built = getBuilt`a\href{http://example.com/}{+b}`; expect(built).toMatchSnapshot(); }); }); describe("A raw text parser", function() { it("should not not parse a mal-formed string", function() { // In the next line, the first character passed to \includegraphics is a // Unicode combining character. So this is a test that the parser will catch a bad string. expect("\\includegraphics[\u030aheight=0.8em, totalheight=0.9em, width=0.9em]{" + "https://cdn.kastatic.org/images/apple-touch-icon-57x57-precomposed.new.png}").not.toParse(); }); // Disabled until https://github.com/KaTeX/KaTeX/pull/1794 is merged. it.skip("should return null for a omitted optional string", function() { expect("\\includegraphics{https://cdn.kastatic.org/images/apple-touch-icon-57x57-precomposed.new.png}").toParse(); }); }); describe("A parser that does not throw on unsupported commands", function() { // The parser breaks on unsupported commands unless it is explicitly // told not to const errorColor = "#933"; const noThrowSettings = new Settings({ throwOnError: false, errorColor: errorColor, }); it("should still parse on unrecognized control sequences", function() { expect`\error`.toParse(noThrowSettings); }); describe("should allow unrecognized controls sequences anywhere, including", function() { it("in superscripts and subscripts", function() { expect`2_\error`.toBuild(noThrowSettings); expect`3^{\error}_\error`.toBuild(noThrowSettings); expect`\int\nolimits^\error_\error`.toBuild(noThrowSettings); }); it("in fractions", function() { expect`\frac{345}{\error}`.toBuild(noThrowSettings); expect`\frac\error{\error}`.toBuild(noThrowSettings); }); it("in square roots", function() { expect`\sqrt\error`.toBuild(noThrowSettings); expect`\sqrt{234\error}`.toBuild(noThrowSettings); }); it("in text boxes", function() { expect`\text{\error}`.toBuild(noThrowSettings); }); }); it("should produce color nodes with a color value given by errorColor", function() { const parsedInput = getParsed(r`\error`, noThrowSettings); expect(parsedInput[0].type).toBe("color"); expect(parsedInput[0].color).toBe(errorColor); }); it("should build katex-error span for other type of KaTeX error", function() { const built = getBuilt("2^2^2", noThrowSettings); expect(built).toMatchSnapshot(); }); it("should properly escape LaTeX in errors", function() { const html = katex.renderToString("2^&\"<>", noThrowSettings); expect(html).toMatchSnapshot(); }); }); describe("The symbol table integrity", function() { it("should treat certain symbols as synonyms", function() { expect`<`.toBuildLike`\lt`; expect`>`.toBuildLike`\gt`; expect`\left<\frac{1}{x}\right>`.toBuildLike`\left\lt\frac{1}{x}\right\gt`; }); }); describe("Symbols", function() { it("should support AMS symbols in both text and math mode", function() { // These text+math symbols are from Section 6 of // http://mirrors.ctan.org/fonts/amsfonts/doc/amsfonts.pdf const symbols = r`\yen\checkmark\circledR\maltese`; expect(symbols).toBuild(); expect(`\\text{${symbols}}`).toBuild(strictSettings); }); }); describe("A macro expander", function() { it("should produce individual tokens", function() { expect`e^\foo`.toParseLike("e^1 23", new Settings({macros: {"\\foo": "123"}})); }); it("should preserve leading spaces inside macro definition", function() { expect`\text{\foo}`.toParseLike(r`\text{ x}`, new Settings({macros: {"\\foo": " x"}})); }); it("should preserve leading spaces inside macro argument", function() { expect`\text{\foo{ x}}`.toParseLike(r`\text{ x}`, new Settings({macros: {"\\foo": "#1"}})); }); it("should ignore expanded spaces in math mode", function() { expect`\foo`.toParseLike("x", new Settings({macros: {"\\foo": " x"}})); }); it("should consume spaces after control-word macro", function() { expect`\text{\foo }`.toParseLike(r`\text{x}`, new Settings({macros: {"\\foo": "x"}})); }); it("should consume spaces after macro with \\relax", function() { expect`\text{\foo }`.toParseLike(r`\text{}`, new Settings({macros: {"\\foo": "\\relax"}})); }); it("should not consume spaces after control-word expansion", function() { expect`\text{\\ }`.toParseLike(r`\text{ }`, new Settings({macros: {"\\\\": "\\relax"}})); }); it("should consume spaces after \\relax", function() { expect`\text{\relax }`.toParseLike`\text{}`; }); it("should consume spaces after control-word function", function() { expect`\text{\KaTeX }`.toParseLike`\text{\KaTeX}`; }); it("should preserve spaces after control-symbol macro", function() { expect`\text{\% y}`.toParseLike(r`\text{x y}`, new Settings({macros: {"\\%": "x"}})); }); it("should preserve spaces after control-symbol function", function() { expect`\text{\' }`.toParse(); }); it("should consume spaces between arguments", function() { expect`\text{\foo 1 2}`.toParseLike(r`\text{12end}`, new Settings({macros: {"\\foo": "#1#2end"}})); expect`\text{\foo {1} {2}}`.toParseLike(r`\text{12end}`, new Settings({macros: {"\\foo": "#1#2end"}})); }); it("should allow for multiple expansion", function() { expect`1\foo2`.toParseLike("1aa2", new Settings({macros: { "\\foo": "\\bar\\bar", "\\bar": "a", }})); }); it("should allow for multiple expansion with argument", function() { expect`1\foo2`.toParseLike("12222", new Settings({macros: { "\\foo": "\\bar{#1}\\bar{#1}", "\\bar": "#1#1", }})); }); it("should allow for macro argument", function() { expect`\foo\bar`.toParseLike("(x)", new Settings({macros: { "\\foo": "(#1)", "\\bar": "x", }})); }); it("should allow for space macro argument (text version)", function() { expect`\text{\foo\bar}`.toParseLike(r`\text{( )}`, new Settings({macros: { "\\foo": "(#1)", "\\bar": " ", }})); }); it("should allow for space macro argument (math version)", function() { expect`\foo\bar`.toParseLike("()", new Settings({macros: { "\\foo": "(#1)", "\\bar": " ", }})); }); it("should allow for space second argument (text version)", function() { expect`\text{\foo\bar\bar}`.toParseLike(r`\text{( , )}`, new Settings({macros: { "\\foo": "(#1,#2)", "\\bar": " ", }})); }); it("should allow for space second argument (math version)", function() { expect`\foo\bar\bar`.toParseLike("(,)", new Settings({macros: { "\\foo": "(#1,#2)", "\\bar": " ", }})); }); it("should allow for empty macro argument", function() { expect`\foo\bar`.toParseLike("()", new Settings({macros: { "\\foo": "(#1)", "\\bar": "", }})); }); // TODO: The following is not currently possible to get working, given that // functions and macros are dealt with separately. /* it("should allow for space function arguments", function() { expect`\frac\bar\bar`.toParseLike(r`\frac{}{}`, new Settings({macros: { "\\bar": " ", }})); }); */ it("should build \\overset and \\underset", function() { expect`\overset{f}{\rightarrow} Y`.toBuild(); expect("\\underset{f}{\\rightarrow} Y").toBuild(); }); it("should build \\iff, \\implies, \\impliedby", function() { expect`X \iff Y`.toBuild(); expect`X \implies Y`.toBuild(); expect`X \impliedby Y`.toBuild(); }); it("should allow aliasing characters", function() { expect`x’=c`.toParseLike("x'=c", new Settings({macros: { "’": "'", }})); }); it("\\@firstoftwo should consume both, and avoid errors", function() { expect`\@firstoftwo{yes}{no}`.toParseLike`yes`; expect`\@firstoftwo{yes}{1'_2^3}`.toParseLike`yes`; }); it("\\@ifstar should consume star but nothing else", function() { expect`\@ifstar{yes}{no}*!`.toParseLike`yes!`; expect`\@ifstar{yes}{no}?!`.toParseLike`no?!`; }); it("\\@ifnextchar should not consume anything", function() { expect`\@ifnextchar!{yes}{no}!!`.toParseLike`yes!!`; expect`\@ifnextchar!{yes}{no}?!`.toParseLike`no?!`; }); it("\\@ifstar should consume star but nothing else", function() { expect`\@ifstar{yes}{no}*!`.toParseLike`yes!`; expect`\@ifstar{yes}{no}?!`.toParseLike`no?!`; }); it("\\TextOrMath should work immediately", function() { expect`\TextOrMath{text}{math}`.toParseLike`math`; }); it("\\TextOrMath should work after other math", function() { expect`x+\TextOrMath{text}{math}`.toParseLike`x+math`; }); it("\\TextOrMath should work immediately after \\text", function() { expect`\text{\TextOrMath{text}{math}}`.toParseLike`\text{text}`; }); it("\\TextOrMath should work later after \\text", function() { expect`\text{hello \TextOrMath{text}{math}}`.toParseLike`\text{hello text}`; }); it("\\TextOrMath should work immediately after \\text ends", function() { expect`\text{\TextOrMath{text}{math}}\TextOrMath{text}{math}` .toParseLike`\text{text}math`; }); it("\\TextOrMath should work immediately after $", function() { expect`\text{$\TextOrMath{text}{math}$}`.toParseLike`\text{$math$}`; }); it("\\TextOrMath should work later after $", function() { expect`\text{$x+\TextOrMath{text}{math}$}`.toParseLike`\text{$x+math$}`; }); it("\\TextOrMath should work immediately after $ ends", function() { expect`\text{$\TextOrMath{text}{math}$\TextOrMath{text}{math}}` .toParseLike`\text{$math$text}`; }); it("\\TextOrMath should work in a macro", function() { expect`\mode\text{\mode$\mode$\mode}\mode` .toParseLike(r`math\text{text$math$text}math`, new Settings({macros: { "\\mode": "\\TextOrMath{text}{math}", }})); }); it("\\TextOrMath should work in a macro passed to \\text", function() { expect`\text\mode`.toParseLike(r`\text t`, new Settings({macros: {"\\mode": "\\TextOrMath{t}{m}"}})); }); it("\\char produces literal characters", () => { expect("\\char`a").toParseLike("\\char`\\a"); expect("\\char`\\%").toParseLike("\\char37"); expect("\\char`\\%").toParseLike("\\char'45"); expect("\\char`\\%").toParseLike('\\char"25'); expect("\\char").not.toParse(); expect("\\char`").not.toParse(); expect("\\char'").not.toParse(); expect('\\char"').not.toParse(); expect("\\char'a").not.toParse(); expect('\\char"g').not.toParse(); expect('\\char"g').not.toParse(); }); it("should build Unicode private area characters", function() { expect`\gvertneqq\lvertneqq\ngeqq\ngeqslant\nleqq`.toBuild(); expect`\nleqslant\nshortmid\nshortparallel\varsubsetneq`.toBuild(); expect`\varsubsetneqq\varsupsetneq\varsupsetneqq`.toBuild(); }); // TODO(edemaine): This doesn't work yet. Parses like `\text text`, // which doesn't treat all four letters as an argument. //it("\\TextOrMath should work in a macro passed to \\text", function() { // expect`\text\mode`.toParseLike(r`\text{text}`, new Settings({macros: // {"\\mode": "\\TextOrMath{text}{math}"}); //}); it("\\gdef defines macros", function() { expect`\gdef\foo{x^2}\foo+\foo`.toParseLike`x^2+x^2`; expect`\gdef{\foo}{x^2}\foo+\foo`.toParseLike`x^2+x^2`; expect`\gdef\foo{hi}\foo+\text{\foo}`.toParseLike`hi+\text{hi}`; expect`\gdef\foo#1{hi #1}\text{\foo{Alice}, \foo{Bob}}` .toParseLike`\text{hi Alice, hi Bob}`; expect`\gdef\foo#1#2{(#1,#2)}\foo 1 2+\foo 3 4`.toParseLike`(1,2)+(3,4)`; expect`\gdef\foo#2{}`.not.toParse(); expect`\gdef\foo#1#3{}`.not.toParse(); expect`\gdef\foo#1#2#3#4#5#6#7#8#9{}`.toParse(); expect`\gdef\foo#1#2#3#4#5#6#7#8#9#10{}`.not.toParse(); expect`\gdef\foo#{}`.not.toParse(); expect`\gdef\foo\bar`.toParse(); expect`\gdef{\foo\bar}{}`.not.toParse(); expect`\gdef{}{}`.not.toParse(); // TODO: These shouldn't work, but `1` and `{1}` are currently treated // the same, as are `\foo` and `{\foo}`. //expect`\gdef\foo1`.not.toParse(); //expect`\gdef{\foo}{}`.not.toParse(); }); it("\\def works locally", () => { expect("\\def\\x{1}\\x{\\def\\x{2}\\x{\\def\\x{3}\\x}\\x}\\x") .toParseLike`1{2{3}2}1`; expect("\\def\\x{1}\\x\\def\\x{2}\\x{\\def\\x{3}\\x\\def\\x{4}\\x}\\x") .toParseLike`12{34}2`; }); it("\\gdef overrides at all levels", () => { expect("\\def\\x{1}\\x{\\def\\x{2}\\x{\\gdef\\x{3}\\x}\\x}\\x") .toParseLike`1{2{3}3}3`; expect("\\def\\x{1}\\x{\\def\\x{2}\\x{\\global\\def\\x{3}\\x}\\x}\\x") .toParseLike`1{2{3}3}3`; expect("\\def\\x{1}\\x{\\def\\x{2}\\x{\\gdef\\x{3}\\x\\def\\x{4}\\x}" + "\\x\\def\\x{5}\\x}\\x").toParseLike`1{2{34}35}3`; }); it("\\global needs to followed by \\def", () => { expect`\global\def\foo{}\foo`.toParseLike``; // TODO: This doesn't work yet; \global needs to expand argument. //expect`\def\DEF{\def}\global\DEF\foo{}\foo`.toParseLike``; expect`\global\foo`.not.toParse(); expect`\global\bar x`.not.toParse(); }); it("Macro arguments do not generate groups", () => { expect("\\def\\x{1}\\x\\def\\foo#1{#1}\\foo{\\x\\def\\x{2}\\x}\\x") .toParseLike`1122`; }); it("\\textbf arguments do generate groups", () => { expect("\\def\\x{1}\\x\\textbf{\\x\\def\\x{2}\\x}\\x") .toParseLike`1\textbf{12}1`; }); it("\\sqrt optional arguments generate groups", () => { expect("\\def\\x{1}\\def\\y{1}\\x\\y" + "\\sqrt[\\def\\x{2}\\x]{\\def\\y{2}\\y}\\x\\y") .toParseLike`11\sqrt[2]{2}11`; }); it("\\gdef changes settings.macros", () => { const macros = {}; expect`\gdef\foo{1}`.toParse(new Settings({macros})); expect(macros["\\foo"]).toBeTruthy(); }); it("\\def doesn't change settings.macros", () => { const macros = {}; expect`\def\foo{1}`.toParse(new Settings({macros})); expect(macros["\\foo"]).toBeFalsy(); }); it("\\newcommand defines new macros", () => { expect`\newcommand\foo{x^2}\foo+\foo`.toParseLike`x^2+x^2`; expect`\newcommand{\foo}{x^2}\foo+\foo`.toParseLike`x^2+x^2`; // Function detection expect`\newcommand\bar{x^2}\bar+\bar`.not.toParse(); expect`\newcommand{\bar}{x^2}\bar+\bar`.not.toParse(); // Symbol detection expect`\newcommand\lambda{x^2}\lambda`.not.toParse(); expect`\newcommand\textdollar{x^2}\textdollar`.not.toParse(); // Macro detection expect`\newcommand{\foo}{1}\foo\newcommand{\foo}{2}\foo`.not.toParse(); // Implicit detection expect`\newcommand\limits{}`.not.toParse(); }); it("\\renewcommand redefines macros", () => { expect`\renewcommand\foo{x^2}\foo+\foo`.not.toParse(); expect`\renewcommand{\foo}{x^2}\foo+\foo`.not.toParse(); expect`\renewcommand\bar{x^2}\bar+\bar`.toParseLike`x^2+x^2`; expect`\renewcommand{\bar}{x^2}\bar+\bar`.toParseLike`x^2+x^2`; expect`\newcommand{\foo}{1}\foo\renewcommand{\foo}{2}\foo`.toParseLike`12`; }); it("\\providecommand (re)defines macros", () => { expect`\providecommand\foo{x^2}\foo+\foo`.toParseLike`x^2+x^2`; expect`\providecommand{\foo}{x^2}\foo+\foo`.toParseLike`x^2+x^2`; expect`\providecommand\bar{x^2}\bar+\bar`.toParseLike`x^2+x^2`; expect`\providecommand{\bar}{x^2}\bar+\bar`.toParseLike`x^2+x^2`; expect`\newcommand{\foo}{1}\foo\providecommand{\foo}{2}\foo` .toParseLike`12`; expect`\providecommand{\foo}{1}\foo\renewcommand{\foo}{2}\foo` .toParseLike`12`; expect`\providecommand{\foo}{1}\foo\providecommand{\foo}{2}\foo` .toParseLike`12`; }); it("\\newcommand is local", () => { expect`\newcommand\foo{1}\foo{\renewcommand\foo{2}\foo}\foo` .toParseLike`1{2}1`; }); it("\\newcommand accepts number of arguments", () => { expect`\newcommand\foo[1]{#1^2}\foo x+\foo{y}`.toParseLike`x^2+y^2`; expect`\newcommand\foo[10]{#1^2}\foo 0123456789`.toParseLike`0^2`; expect`\newcommand\foo[x]{}`.not.toParse(); expect`\newcommand\foo[1.5]{}`.not.toParse(); }); // This may change in the future, if we support the extra features of // \hspace. it("should treat \\hspace, \\hskip like \\kern", function() { expect`\hspace{1em}`.toParseLike`\kern1em`; expect`\hskip{1em}`.toParseLike`\kern1em`; }); it("should expand \\limsup as expected", () => { expect`\limsup`.toParseLike`\mathop{\operatorname{lim\,sup}}\limits`; }); it("should expand \\liminf as expected", () => { expect`\liminf`.toParseLike`\mathop{\operatorname{lim\,inf}}\limits`; }); it("should expand \\argmin as expected", () => { expect`\argmin`.toParseLike`\mathop{\operatorname{arg\,min}}\limits`; }); it("should expand \\argmax as expected", () => { expect`\argmax`.toParseLike`\mathop{\operatorname{arg\,max}}\limits`; }); }); describe("\\tag support", function() { const displayMode = new Settings({displayMode: true}); it("should fail outside display mode", () => { expect`\tag{hi}x+y`.not.toParse(); }); it("should fail with multiple tags", () => { expect`\tag{1}\tag{2}x+y`.not.toParse(displayMode); }); it("should build", () => { expect`\tag{hi}x+y`.toBuild(displayMode); }); it("should ignore location of \\tag", () => { expect`\tag{hi}x+y`.toParseLike(r`x+y\tag{hi}`, displayMode); }); it("should handle \\tag* like \\tag", () => { expect`\tag{hi}x+y`.toParseLike(r`\tag*{({hi})}x+y`, displayMode); }); }); describe("leqno and fleqn rendering options", () => { const expr = r`\tag{hi}x+y`; for (const opt of ["leqno", "fleqn"]) { it(`should not add ${opt} class by default`, () => { const settings = new Settings({displayMode: true}); const built = katex.__renderToDomTree(expr, settings); expect(built.classes).not.toContain(opt); }); it(`should not add ${opt} class when false`, () => { const settings = new Settings({displayMode: true}); settings[opt] = false; const built = katex.__renderToDomTree(expr, settings); expect(built.classes).not.toContain(opt); }); it(`should add ${opt} class when true`, () => { const settings = new Settings({displayMode: true}); settings[opt] = true; const built = katex.__renderToDomTree(expr, settings); expect(built.classes).toContain(opt); }); } }); describe("\\@binrel automatic bin/rel/ord", () => { it("should generate proper class", () => { expect("L\\@binrel+xR").toParseLike("L\\mathbin xR"); expect("L\\@binrel=xR").toParseLike("L\\mathrel xR"); expect("L\\@binrel xxR").toParseLike("L\\mathord xR"); expect("L\\@binrel{+}{x}R").toParseLike("L\\mathbin{{x}}R"); expect("L\\@binrel{=}{x}R").toParseLike("L\\mathrel{{x}}R"); expect("L\\@binrel{x}{x}R").toParseLike("L\\mathord{{x}}R"); }); it("should base on just first character in group", () => { expect("L\\@binrel{+x}xR").toParseLike("L\\mathbin xR"); expect("L\\@binrel{=x}xR").toParseLike("L\\mathrel xR"); expect("L\\@binrel{xx}xR").toParseLike("L\\mathord xR"); }); }); describe("A parser taking String objects", function() { it("should not fail on an empty String object", function() { expect(new String("")).toParse(); }); it("should parse the same as a regular string", function() { expect(new String("xy")).toParseLike`xy`; expect(new String(r`\div`)).toParseLike`\div`; expect(new String(r`\frac 1 2`)).toParseLike`\frac 1 2`; }); }); describe("Unicode accents", function() { it("should parse Latin-1 letters in math mode", function() { // TODO(edemaine): Unsupported Latin-1 letters in math: ÇÐÞçðþ expect`ÀÁÂÃÄÅÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåèéêëìíîïñòóôõöùúûüýÿ` .toParseLike( r`\grave A\acute A\hat A\tilde A\ddot A\mathring A` + r`\grave E\acute E\hat E\ddot E` + r`\grave I\acute I\hat I\ddot I` + r`\tilde N` + r`\grave O\acute O\hat O\tilde O\ddot O` + r`\grave U\acute U\hat U\ddot U` + r`\acute Y` + r`\grave a\acute a\hat a\tilde a\ddot a\mathring a` + r`\grave e\acute e\hat e\ddot e` + r`\grave ı\acute ı\hat ı\ddot ı` + r`\tilde n` + r`\grave o\acute o\hat o\tilde o\ddot o` + r`\grave u\acute u\hat u\ddot u` + r`\acute y\ddot y`, nonstrictSettings); }); it("should parse Latin-1 letters in text mode", function() { // TODO(edemaine): Unsupported Latin-1 letters in text: ÇÐÞçðþ expect`\text{ÀÁÂÃÄÅÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåèéêëìíîïñòóôõöùúûüýÿ}` .toParseLike( r`\text{\`A\'A\^A\~A\"A\r A` + r`\`E\'E\^E\"E` + r`\`I\'I\^I\"I` + r`\~N` + r`\`O\'O\^O\~O\"O` + r`\`U\'U\^U\"U` + r`\'Y` + r`\`a\'a\^a\~a\"a\r a` + r`\`e\'e\^e\"e` + r`\`ı\'ı\^ı\"ı` + r`\~n` + r`\`o\'o\^o\~o\"o` + r`\`u\'u\^u\"u` + r`\'y\"y}`, strictSettings); }); it("should support \\aa in text mode", function() { expect`\text{\aa\AA}`.toParseLike(r`\text{\r a\r A}`, strictSettings); expect`\aa`.not.toParse(strictSettings); expect`\Aa`.not.toParse(strictSettings); }); it("should parse combining characters", function() { expect("A\u0301C\u0301").toParseLike(r`Á\acute C`, nonstrictSettings); expect("\\text{A\u0301C\u0301}").toParseLike(r`\text{Á\'C}`, strictSettings); }); it("should parse multi-accented characters", function() { expect`ấā́ắ\text{ấā́ắ}`.toParse(nonstrictSettings); // Doesn't parse quite the same as // "\\text{\\'{\\^a}\\'{\\=a}\\'{\\u a}}" because of the ordgroups. }); it("should parse accented i's and j's", function() { expect`íȷ́`.toParseLike(r`\acute ı\acute ȷ`, nonstrictSettings); expect`ấā́ắ\text{ấā́ắ}`.toParse(nonstrictSettings); }); }); describe("Unicode", function() { it("should parse negated relations", function() { expect`∉∤∦≁≆≠≨≩≮≯≰≱⊀⊁⊈⊉⊊⊋⊬⊭⊮⊯⋠⋡⋦⋧⋨⋩⋬⋭⪇⪈⪉⪊⪵⪶⪹⪺⫋⫌`.toParse(strictSettings); }); it("should build relations", function() { expect`∈∋∝∼∽≂≃≅≈≊≍≎≏≐≑≒≓≖≗≜≡≤≥≦≧≪≫≬≳≷≺≻≼≽≾≿∴∵∣≔≕⩴⋘⋙⟂⊨∌`.toBuild(strictSettings); }); it("should build big operators", function() { expect`∏∐∑∫∬∭∮⋀⋁⋂⋃⨀⨁⨂⨄⨆`.toBuild(strictSettings); }); it("should build more relations", function() { expect`⊂⊃⊆⊇⊏⊐⊑⊒⊢⊣⊩⊪⊸⋈⋍⋐⋑⋔⋛⋞⋟⌢⌣⩾⪆⪌⪕⪖⪯⪰⪷⪸⫅⫆≘≙≚≛≝≞≟≲⩽⪅≶⋚⪋`.toBuild(strictSettings); }); it("should parse symbols", function() { expect("£¥ℂℍℑℎℓℕ℘ℙℚℜℝℤℲℵðℶℷℸ⅁∀∁∂∃∇∞∠∡∢♠♡♢♣♭♮♯✓°¬‼⋮\u00B7\u00A9").toBuild(strictSettings); expect("\\text{£¥ℂℍℎ\u00A9\u00AE\uFE0F}").toBuild(strictSettings); }); it("should build Greek capital letters", function() { expect("\u0391\u0392\u0395\u0396\u0397\u0399\u039A\u039C\u039D" + "\u039F\u03A1\u03A4\u03A7").toBuild(strictSettings); }); it("should build arrows", function() { expect`←↑→↓↔↕↖↗↘↙↚↛↞↠↢↣↦↩↪↫↬↭↮↰↱↶↷↼↽↾↾↿⇀⇁⇂⇃⇄⇆⇇⇈⇉`.toBuild(strictSettings); }); it("should build more arrows", function() { expect`⇊⇋⇌⇍⇎⇏⇐⇑⇒⇓⇔⇕⇚⇛⇝⟵⟶⟷⟸⟹⟺⟼`.toBuild(strictSettings); }); it("should build binary operators", function() { expect("±×÷∓∔∧∨∩∪≀⊎⊓⊔⊕⊖⊗⊘⊙⊚⊛⊝⊞⊟⊠⊡⊺⊻⊼⋇⋉⋊⋋⋌⋎⋏⋒⋓⩞\u22C5").toBuild(strictSettings); }); it("should build delimiters", function() { expect("\\left\u230A\\frac{a}{b}\\right\u230B").toBuild(); expect("\\left\u2308\\frac{a}{b}\\right\u2308").toBuild(); expect("\\left\u27ee\\frac{a}{b}\\right\u27ef").toBuild(); expect("\\left\u27e8\\frac{a}{b}\\right\u27e9").toBuild(); expect("\\left\u23b0\\frac{a}{b}\\right\u23b1").toBuild(); expect`┌x┐ └x┘`.toBuild(); expect("\u231Cx\u231D \u231Ex\u231F").toBuild(); expect("\u27E6x\u27E7").toBuild(); expect("\\llbracket \\rrbracket").toBuild(); expect("\\lBrace \\rBrace").toBuild(); }); it("should build some surrogate pairs", function() { let wideCharStr = ""; wideCharStr += String.fromCharCode(0xD835, 0xDC00); // bold A wideCharStr += String.fromCharCode(0xD835, 0xDC68); // bold italic A wideCharStr += String.fromCharCode(0xD835, 0xDD04); // Fraktur A wideCharStr += String.fromCharCode(0xD835, 0xDD38); // double-struck wideCharStr += String.fromCharCode(0xD835, 0xDC9C); // script A wideCharStr += String.fromCharCode(0xD835, 0xDDA0); // sans serif A wideCharStr += String.fromCharCode(0xD835, 0xDDD4); // bold sans A wideCharStr += String.fromCharCode(0xD835, 0xDE08); // italic sans A wideCharStr += String.fromCharCode(0xD835, 0xDE70); // monospace A wideCharStr += String.fromCharCode(0xD835, 0xDFCE); // bold zero wideCharStr += String.fromCharCode(0xD835, 0xDFE2); // sans serif zero wideCharStr += String.fromCharCode(0xD835, 0xDFEC); // bold sans zero wideCharStr += String.fromCharCode(0xD835, 0xDFF6); // monospace zero expect(wideCharStr).toBuild(strictSettings); let wideCharText = "\text{"; wideCharText += String.fromCharCode(0xD835, 0xDC00); // bold A wideCharText += String.fromCharCode(0xD835, 0xDC68); // bold italic A wideCharText += String.fromCharCode(0xD835, 0xDD04); // Fraktur A wideCharText += String.fromCharCode(0xD835, 0xDD38); // double-struck wideCharText += String.fromCharCode(0xD835, 0xDC9C); // script A wideCharText += String.fromCharCode(0xD835, 0xDDA0); // sans serif A wideCharText += String.fromCharCode(0xD835, 0xDDD4); // bold sans A wideCharText += String.fromCharCode(0xD835, 0xDE08); // italic sans A wideCharText += String.fromCharCode(0xD835, 0xDE70); // monospace A wideCharText += String.fromCharCode(0xD835, 0xDFCE); // bold zero wideCharText += String.fromCharCode(0xD835, 0xDFE2); // sans serif zero wideCharText += String.fromCharCode(0xD835, 0xDFEC); // bold sans zero wideCharText += String.fromCharCode(0xD835, 0xDFF6); // monospace zero wideCharText += "}"; expect(wideCharText).toBuild(strictSettings); }); }); describe("The maxSize setting", function() { const rule = r`\rule{999em}{999em}`; it("should clamp size when set", function() { const built = getBuilt(rule, new Settings({maxSize: 5}))[0]; expect(built.style.borderRightWidth).toEqual("5em"); expect(built.style.borderTopWidth).toEqual("5em"); }); it("should not clamp size when not set", function() { const built = getBuilt(rule)[0]; expect(built.style.borderRightWidth).toEqual("999em"); expect(built.style.borderTopWidth).toEqual("999em"); }); it("should make zero-width rules if a negative maxSize is passed", function() { const built = getBuilt(rule, new Settings({maxSize: -5}))[0]; expect(built.style.borderRightWidth).toEqual("0em"); expect(built.style.borderTopWidth).toEqual("0em"); }); }); describe("The maxExpand setting", () => { it("should prevent expansion", () => { expect`\gdef\foo{1}\foo`.toParse(); expect`\gdef\foo{1}\foo`.toParse(new Settings({maxExpand: 2})); expect`\gdef\foo{1}\foo`.not.toParse(new Settings({maxExpand: 1})); expect`\gdef\foo{1}\foo`.not.toParse(new Settings({maxExpand: 0})); }); it("should prevent infinite loops", () => { expect`\gdef\foo{\foo}\foo`.not.toParse( new Settings({maxExpand: 10})); }); }); describe("The \\mathchoice function", function() { const cmd = r`\sum_{k = 0}^{\infty} x^k`; it("should render as if there is nothing other in display math", function() { expect(`\\displaystyle\\mathchoice{${cmd}}{T}{S}{SS}`) .toBuildLike(`\\displaystyle${cmd}`); }); it("should render as if there is nothing other in text", function() { expect(`\\mathchoice{D}{${cmd}}{S}{SS}`).toBuildLike(cmd); }); it("should render as if there is nothing other in scriptstyle", function() { expect(`x_{\\mathchoice{D}{T}{${cmd}}{SS}}`).toBuildLike(`x_{${cmd}}`); }); it("should render as if there is nothing other in scriptscriptstyle", function() { expect(`x_{y_{\\mathchoice{D}{T}{S}{${cmd}}}}`).toBuildLike(`x_{y_{${cmd}}}`); }); }); describe("Newlines via \\\\ and \\newline", function() { it("should build \\\\ and \\newline the same", () => { expect`hello \\ world`.toBuildLike`hello \newline world`; expect`hello \\[1ex] world`.toBuildLike( "hello \\newline[1ex] world"); }); it("should not allow \\cr at top level", () => { expect`hello \cr world`.not.toBuild(); }); it("array redefines and resets \\\\", () => { expect`a\\b\begin{matrix}x&y\\z&w\end{matrix}\\c` .toParseLike`a\newline b\begin{matrix}x&y\cr z&w\end{matrix}\newline c`; }); it("\\\\ causes newline, even after mrel and mop", () => { const markup = katex.renderToString(r`M = \\ a + \\ b \\ c`); // Ensure newlines appear outside base spans (because, in this regexp, // base span occurs immediately after each newline span). expect(markup).toMatch( /(.*?<\/span><\/span>){3}/); expect(markup).toMatchSnapshot(); }); }); describe("Symbols", function() { it("should parse \\text{\\i\\j}", () => { expect`\text{\i\j}`.toBuild(strictSettings); }); it("should parse spacing functions in math or text mode", () => { expect`A\;B\,C\nobreakspace \text{A\;B\,C\nobreakspace}`.toBuild(strictSettings); }); it("should render ligature commands like their unicode characters", () => { expect`\text{\ae\AE\oe\OE\o\O\ss}`.toBuildLike(r`\text{æÆœŒøØß}`, strictSettings); }); }); describe("strict setting", function() { it("should allow unicode text when not strict", () => { expect`é`.toParse(new Settings(nonstrictSettings)); expect`試`.toParse(new Settings(nonstrictSettings)); expect`é`.toParse(new Settings({strict: "ignore"})); expect`試`.toParse(new Settings({strict: "ignore"})); expect`é`.toParse(new Settings({strict: () => false})); expect`試`.toParse(new Settings({strict: () => false})); expect`é`.toParse(new Settings({strict: () => "ignore"})); expect`試`.toParse(new Settings({strict: () => "ignore"})); }); it("should forbid unicode text when strict", () => { expect`é`.not.toParse(new Settings({strict: true})); expect`試`.not.toParse(new Settings({strict: true})); expect`é`.not.toParse(new Settings({strict: "error"})); expect`試`.not.toParse(new Settings({strict: "error"})); expect`é`.not.toParse(new Settings({strict: () => true})); expect`試`.not.toParse(new Settings({strict: () => true})); expect`é`.not.toParse(new Settings({strict: () => "error"})); expect`試`.not.toParse(new Settings({strict: () => "error"})); }); it("should warn about unicode text when default", () => { expect`é`.toWarn(new Settings()); expect`試`.toWarn(new Settings()); }); it("should always allow unicode text in text mode", () => { expect`\text{é試}`.toParse(nonstrictSettings); expect`\text{é試}`.toParse(strictSettings); expect`\text{é試}`.toParse(); }); it("should warn about top-level \\newline in display mode", () => { expect`x\\y`.toWarn(new Settings({displayMode: true})); expect`x\\y`.toParse(new Settings({displayMode: false})); }); }); describe("Internal __* interface", function() { const latex = r`\sum_{k = 0}^{\infty} x^k`; const rendered = katex.renderToString(latex); it("__parse renders same as renderToString", () => { const parsed = katex.__parse(latex); expect(buildTree(parsed, latex, new Settings()).toMarkup()).toEqual(rendered); }); it("__renderToDomTree renders same as renderToString", () => { const tree = katex.__renderToDomTree(latex); expect(tree.toMarkup()).toEqual(rendered); }); it("__renderToHTMLTree renders same as renderToString sans MathML", () => { const tree = katex.__renderToHTMLTree(latex); const renderedSansMathML = rendered.replace( /.*?<\/span>/, ''); expect(tree.toMarkup()).toEqual(renderedSansMathML); }); }); describe("Extending katex by new fonts and symbols", function() { beforeAll(() => { const fontName = "mockEasternArabicFont"; // add eastern arabic numbers to symbols table // these symbols are ۰۱۲۳۴۵۶۷۸۹ and ٠١٢٣٤٥٦٧٨٩ for (let number = 0; number <= 9; number++) { const persianNum = String.fromCharCode(0x0660 + number); katex.__defineSymbol( "math", fontName, "textord", persianNum, persianNum); const arabicNum = String.fromCharCode(0x06F0 + number); katex.__defineSymbol( "math", fontName, "textord", arabicNum, arabicNum); } }); it("should throw on rendering new symbols with no font metrics", () => { // Lets parse 99^11 in eastern arabic const errorMessage = "Font metrics not found for font: mockEasternArabicFont-Regular."; expect(() => { katex.__renderToDomTree("۹۹^{۱۱}", strictSettings); }).toThrow(errorMessage); }); it("should add font metrics to metrics map and render successfully", () => { const mockMetrics = {}; // mock font metrics for the symbols that we added previously for (let number = 0; number <= 9; number++) { mockMetrics[0x0660 + number] = [-0.00244140625, 0.6875, 0, 0]; mockMetrics[0x06F0 + number] = [-0.00244140625, 0.6875, 0, 0]; } katex.__setFontMetrics('mockEasternArabicFont-Regular', mockMetrics); expect`۹۹^{۱۱}`.toBuild(); }); it("Add new font class to new extended symbols", () => { expect(katex.renderToString("۹۹^{۱۱}")).toMatchSnapshot(); }); });