/* eslint max-len:0 */ /* global expect: false */ /* global it: false */ /* global describe: 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 { defaultSettings, _getBuilt, 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(); }); it("should ignore whitespace", function() { expect(" x y ").toParseLike("xy"); }); it("should ignore whitespace in atom", function() { expect(" x ^ y ").toParseLike("x^y"); }); }); 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); expect(parse).toBeTruthy(); 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.length).toBe(expression.length); }); }); describe("A bin parser", function() { const expression = "+-*\\cdot\\pm\\div"; it("should not fail", function() { expect(expression).toParse(); }); it("should build a list of bins", function() { const parse = getParsed(expression); expect(parse).toBeTruthy(); for (let i = 0; i < parse.length; i++) { const group = parse[i]; expect(group.type).toEqual("bin"); } }); }); describe("A rel parser", function() { const expression = "=<>\\leq\\geq\\neq\\nleq\\ngeq\\cong"; const notExpression = "\\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); expect(parse).toBeTruthy(); for (let i = 0; i < parse.length; i++) { const group = parse[i]; expect(group.type).toEqual("rel"); } }); }); describe("A punct parser", function() { const expression = ",;\\colon"; it("should not fail", function() { expect(expression).toParse(); }); it("should build a list of puncts", function() { const parse = getParsed(expression); expect(parse).toBeTruthy(); for (let i = 0; i < parse.length; i++) { const group = parse[i]; expect(group.type).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); expect(parse).toBeTruthy(); for (let i = 0; i < parse.length; i++) { const group = parse[i]; expect(group.type).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); expect(parse).toBeTruthy(); for (let i = 0; i < parse.length; i++) { const group = parse[i]; expect(group.type).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.value.base).toBeDefined(); expect(parse.value.sup).toBeDefined(); expect(parse.value.sub).toBeUndefined(); }); it("should produce supsubs for subscript", function() { const parse = getParsed("x_3")[0]; expect(parse.type).toBe("supsub"); expect(parse.value.base).toBeDefined(); expect(parse.value.sub).toBeDefined(); expect(parse.value.sup).toBeUndefined(); }); it("should produce supsubs for ^_", function() { const parse = getParsed("x^2_3")[0]; expect(parse.type).toBe("supsub"); expect(parse.value.base).toBeDefined(); expect(parse.value.sup).toBeDefined(); expect(parse.value.sub).toBeDefined(); }); it("should produce supsubs for _^", function() { const parse = getParsed("x_3^2")[0]; expect(parse.type).toBe("supsub"); expect(parse.value.base).toBeDefined(); expect(parse.value.sup).toBeDefined(); expect(parse.value.sub).toBeDefined(); }); it("should produce the same thing regardless of order", function() { const parseA = stripPositions(getParsed("x^2_3")); const parseB = stripPositions(getParsed("x_3^2")); expect(parseA).toEqual(parseB); }); it("should not parse double subscripts or superscripts", function() { expect("x^x^x").toNotParse(); expect("x_x_x").toNotParse(); expect("x_x^x_x").toNotParse(); expect("x_x^x^x").toNotParse(); expect("x^x_x_x").toNotParse(); expect("x^x_x^x").toNotParse(); }); 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").toNotParse(); expect("\\sqrt\\limits_2^2").toNotParse(); expect("45 +\\nolimits 45").toNotParse(); }); 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].value.base.value.limits).toBe(true); parsedInput = getParsed("\\int\\limits_2\\nolimits^2"); expect(parsedInput[0].value.base.value.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.length).toBe(1); const ord = parse[0]; expect(ord.type).toMatch("ord"); expect(ord.value).toBeTruthy(); }); }); 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.length).toBe(1); const sizing = parse[0]; expect(sizing.type).toEqual("sizing"); expect(sizing.value).toBeTruthy(); }); it("should apply only after the function", function() { const parse = getParsed("a \\Large abc"); expect(parse.length).toBe(2); const sizing = parse[1]; expect(sizing.type).toEqual("sizing"); expect(sizing.value.value.length).toBe(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.value[1]; expect(sizing.type).toEqual("sizing"); expect(sizing.value.value.length).toBe(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").toNotParse(); }); it("should not parse 2 argument functions with 0 or 1 arguments", function() { expect("\\frac").toNotParse(); expect("\\frac 1").toNotParse(); }); it("should not parse a function with text right after it", function() { expect("\\redx").toNotParse(); }); 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 = "\\frac{x}{y}"; const dfracExpression = "\\dfrac{x}{y}"; const tfracExpression = "\\tfrac{x}{y}"; 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.value.numer).toBeDefined(); expect(parse.value.denom).toBeDefined(); }); it("should also parse dfrac and tfrac", function() { expect(dfracExpression).toParse(); expect(tfracExpression).toParse(); }); it("should parse dfrac and tfrac as fracs", function() { const dfracParse = getParsed(dfracExpression)[0]; expect(dfracParse.type).toEqual("genfrac"); expect(dfracParse.value.numer).toBeDefined(); expect(dfracParse.value.denom).toBeDefined(); const tfracParse = getParsed(tfracExpression)[0]; expect(tfracParse.type).toEqual("genfrac"); expect(tfracParse.value.numer).toBeDefined(); expect(tfracParse.value.denom).toBeDefined(); }); it("should parse atop", function() { const parse = getParsed("x \\atop y")[0]; expect(parse.type).toEqual("genfrac"); expect(parse.value.numer).toBeDefined(); expect(parse.value.denom).toBeDefined(); expect(parse.value.hasBarLine).toEqual(false); }); }); describe("An over parser", function() { const simpleOver = "1 \\over x"; const complexOver = "1+2i \\over 3+4i"; it("should not fail", function() { expect(simpleOver).toParse(); expect(complexOver).toParse(); }); it("should produce a frac", function() { let parse; parse = getParsed(simpleOver)[0]; expect(parse.type).toEqual("genfrac"); expect(parse.value.numer).toBeDefined(); expect(parse.value.denom).toBeDefined(); parse = getParsed(complexOver)[0]; expect(parse.type).toEqual("genfrac"); expect(parse.value.numer).toBeDefined(); expect(parse.value.denom).toBeDefined(); }); it("should create a numerator from the atoms before \\over", function() { const parse = getParsed(complexOver)[0]; const numer = parse.value.numer; expect(numer.value.length).toEqual(4); }); it("should create a demonimator from the atoms after \\over", function() { const parse = getParsed(complexOver)[0]; const denom = parse.value.numer; expect(denom.value.length).toEqual(4); }); it("should handle empty numerators", function() { const emptyNumerator = "\\over x"; const parse = getParsed(emptyNumerator)[0]; expect(parse.type).toEqual("genfrac"); expect(parse.value.numer).toBeDefined(); expect(parse.value.denom).toBeDefined(); }); it("should handle empty denominators", function() { const emptyDenominator = "1 \\over"; const parse = getParsed(emptyDenominator)[0]; expect(parse.type).toEqual("genfrac"); expect(parse.value.numer).toBeDefined(); expect(parse.value.denom).toBeDefined(); }); it("should handle \\displaystyle correctly", function() { const displaystyleExpression = "\\displaystyle 1 \\over 2"; const parse = getParsed(displaystyleExpression)[0]; expect(parse.type).toEqual("genfrac"); expect(parse.value.numer.value[0].type).toEqual("styling"); expect(parse.value.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 = "{1 \\over 2} \\over 3"; const parse = getParsed(nestedOverExpression)[0]; expect(parse.type).toEqual("genfrac"); expect(parse.value.numer.value[0].type).toEqual("genfrac"); expect(parse.value.numer.value[0].value.numer.value[0].value).toEqual("1"); expect(parse.value.numer.value[0].value.denom.value[0].value).toEqual("2"); expect(parse.value.denom).toBeDefined(); expect(parse.value.denom.value[0].value).toEqual("3"); }); it("should fail with multiple overs in the same group", function() { const badMultipleOvers = "1 \\over 2 + 3 \\over 4"; expect(badMultipleOvers).toNotParse(); const badOverChoose = "1 \\over 2 \\choose 3"; expect(badOverChoose).toNotParse(); }); }); describe("A sizing parser", function() { const sizeExpression = "\\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.value).toBeDefined(); }); }); describe("A text parser", function() { const textExpression = "\\text{a b}"; const noBraceTextExpression = "\\text x"; const nestedTextExpression = "\\text{a {b} \\blue{c} \\textcolor{#fff}{x} \\llap{x}}"; const spaceTextExpression = "\\text{ a \\ }"; const leadingSpaceTextExpression = "\\text {moo}"; const badTextExpression = "\\text{a b%}"; const badFunctionExpression = "\\text{\\sqrt{x}}"; const mathTokenAfterText = "\\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.value).toBeDefined(); }); it("should produce textords instead of mathords", function() { const parse = getParsed(textExpression)[0]; const group = parse.value.body; expect(group[0].type).toEqual("textord"); }); it("should not parse bad text", function() { expect(badTextExpression).toNotParse(); }); it("should not parse bad functions inside text", function() { expect(badFunctionExpression).toNotParse(); }); 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.value.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.value.body.length).toBe(3); expect( parse.value.body.map(function(n) { return n.value; }).join("") ).toBe("moo"); }); it("should parse math within text group", function() { expect("\\text{graph: $y = mx + b$}").toParse(); expect("\\text{graph: \\(y = mx + b\\)}").toParse(); }); it("should parse math within text within math within text", function() { expect("\\text{hello $x + \\text{world $y$} + z$}").toParse(); expect("\\text{hello \\(x + \\text{world $y$} + z\\)}").toParse(); expect("\\text{hello $x + \\text{world \\(y\\)} + z$}").toParse(); expect("\\text{hello \\(x + \\text{world \\(y\\)} + z\\)}").toParse(); }); it("should forbid \\( within math mode", function() { expect("\\(").toNotParse(); expect("\\text{$\\(x\\)$}").toNotParse(); }); it("should forbid $ within math mode", function() { expect("$x$").toNotParse(); expect("\\text{\\($x$\\)}").toNotParse(); }); it("should detect unbalanced \\)", function() { expect("\\)").toNotParse(); expect("\\text{\\)}").toNotParse(); }); it("should detect unbalanced $", function() { expect("$").toNotParse(); expect("\\text{$}").toNotParse(); }); it("should not mix $ and \\(..\\)", function() { expect("\\text{$x\\)}").toNotParse(); expect("\\text{\\(x$}").toNotParse(); }); 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 color parser", function() { const colorExpression = "\\blue{x}"; const newColorExpression = "\\redA{x}"; const customColorExpression1 = "\\textcolor{#fA6}{x}"; const customColorExpression2 = "\\textcolor{#fA6fA6}{x}"; const badCustomColorExpression1 = "\\textcolor{bad-color}{x}"; const badCustomColorExpression2 = "\\textcolor{#fA6f}{x}"; const badCustomColorExpression3 = "\\textcolor{#gA6}{x}"; const oldColorExpression = "\\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.value.color).toBeDefined(); expect(parse.value.value).toBeDefined(); }); it("should parse a custom color", function() { expect(customColorExpression1).toParse(); expect(customColorExpression2).toParse(); }); it("should correctly extract the custom color", function() { const parse1 = getParsed(customColorExpression1)[0]; const parse2 = getParsed(customColorExpression2)[0]; expect(parse1.value.color).toEqual("#fA6"); expect(parse2.value.color).toEqual("#fA6fA6"); }); it("should not parse a bad custom color", function() { expect(badCustomColorExpression1).toNotParse(); expect(badCustomColorExpression2).toNotParse(); expect(badCustomColorExpression3).toNotParse(); }); 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}").toNotParse(); expect("\\textcolor{red}\\frac12").toNotParse(); }); 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("\\textcolor{#fA6}{xy}", { colorIsTextColor: false, }); }); it("should use two-argument \\color if requested", function() { expect(oldColorExpression).toParseLike("\\textcolor{#fA6}{x}y", { colorIsTextColor: true, }); }); }); describe("A tie parser", function() { const mathTie = "a~b"; const textTie = "\\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.value.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.value.body; expect(parse[2].type).toEqual("spacing"); }); }); describe("A delimiter sizing parser", function() { const normalDelim = "\\bigl |"; const notDelim = "\\bigl x"; const bigDelim = "\\Biggr \\langle"; it("should parse normal delimiters", function() { expect(normalDelim).toParse(); expect(bigDelim).toParse(); }); it("should not parse not-delimiters", function() { expect(notDelim).toNotParse(); }); 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.value.mclass).toEqual("mopen"); expect(rightParse.value.mclass).toEqual("mclose"); }); it("should parse the correct size delimiter", function() { const smallParse = getParsed(normalDelim)[0]; const bigParse = getParsed(bigDelim)[0]; expect(smallParse.value.size).toEqual(1); expect(bigParse.value.size).toEqual(4); }); }); describe("An overline parser", function() { const overline = "\\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}}{=}").toNotParse(); expect("{=}\\llap{\\frac{a}{b}}").toNotParse(); expect("\\sum_{\\clap{\\frac{a}{b}}}").toNotParse(); }); it("should produce a lap", function() { const parse = getParsed("\\mathrlap{\\,/}")[0]; expect(parse.type).toEqual("lap"); }); }); describe("A rule parser", function() { const emRule = "\\rule{1em}{2em}"; const exRule = "\\rule{1ex}{2em}"; const badUnitRule = "\\rule{1au}{2em}"; const noNumberRule = "\\rule{1em}{em}"; const incompleteRule = "\\rule{1em}"; const hardNumberRule = "\\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).toNotParse(); expect(noNumberRule).toNotParse(); }); it("should not parse incomplete rules", function() { expect(incompleteRule).toNotParse(); }); 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.value.width.unit).toEqual("em"); expect(emParse.value.height.unit).toEqual("em"); expect(exParse.value.width.unit).toEqual("ex"); expect(exParse.value.height.unit).toEqual("em"); }); it("should parse the number correctly", function() { const hardNumberParse = getParsed(hardNumberRule)[0]; expect(hardNumberParse.value.width.number).toBeCloseTo(1.24); expect(hardNumberParse.value.height.number).toBeCloseTo(2.45); }); it("should parse negative sizes", function() { const parse = getParsed("\\rule{-1em}{- 0.2em}")[0]; expect(parse.value.width.number).toBeCloseTo(-1); expect(parse.value.height.number).toBeCloseTo(-0.2); }); }); describe("A kern parser", function() { const emKern = "\\kern{1em}"; const exKern = "\\kern{1ex}"; const muKern = "\\mkern{1mu}"; const abKern = "a\\kern{1em}b"; const badUnitRule = "\\kern{1au}"; const noNumberRule = "\\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.value.dimension.unit).toEqual("em"); expect(exParse.value.dimension.unit).toEqual("ex"); expect(muParse.value.dimension.unit).toEqual("mu"); expect(abParse.value.dimension.unit).toEqual("em"); }); it("should not parse invalid units", function() { expect(badUnitRule).toNotParse(); expect(noNumberRule).toNotParse(); }); it("should parse negative sizes", function() { const parse = getParsed("\\kern{-1em}")[0]; expect(parse.value.dimension.number).toBeCloseTo(-1); }); it("should parse positive sizes", function() { const parse = getParsed("\\kern{+1em}")[0]; expect(parse.value.dimension.number).toBeCloseTo(1); }); }); describe("A non-braced kern parser", function() { const emKern = "\\kern1em"; const exKern = "\\kern 1 ex"; const muKern = "\\mkern 1mu"; const abKern1 = "a\\mkern1mub"; const abKern2 = "a\\mkern-1mub"; const abKern3 = "a\\mkern-1mu b"; const badUnitRule = "\\kern1au"; const noNumberRule = "\\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.value.dimension.unit).toEqual("em"); expect(exParse.value.dimension.unit).toEqual("ex"); expect(muParse.value.dimension.unit).toEqual("mu"); expect(abParse1.value.dimension.unit).toEqual("mu"); expect(abParse2.value.dimension.unit).toEqual("mu"); expect(abParse3.value.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.length).toEqual(3); expect(abParse1[0].value).toEqual("a"); expect(abParse1[2].value).toEqual("b"); expect(abParse2.length).toEqual(3); expect(abParse2[0].value).toEqual("a"); expect(abParse2[2].value).toEqual("b"); expect(abParse3.length).toEqual(3); expect(abParse3[0].value).toEqual("a"); expect(abParse3[2].value).toEqual("b"); }); it("should not parse invalid units", function() { expect(badUnitRule).toNotParse(); expect(noNumberRule).toNotParse(); }); it("should parse negative sizes", function() { const parse = getParsed("\\kern-1em")[0]; expect(parse.value.dimension.number).toBeCloseTo(-1); }); it("should parse positive sizes", function() { const parse = getParsed("\\kern+1em")[0]; expect(parse.value.dimension.number).toBeCloseTo(1); }); it("should handle whitespace", function() { const abKern = "a\\mkern\t-\r1 \n mu\nb"; const abParse = getParsed(abKern); expect(abParse.length).toEqual(3); expect(abParse[0].value).toEqual("a"); expect(abParse[1].value.dimension.unit).toEqual("mu"); expect(abParse[2].value).toEqual("b"); }); }); describe("A left/right parser", function() { const normalLeftRight = "\\left( \\dfrac{x}{y} \\right)"; const emptyRight = "\\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.value.left).toEqual("("); expect(parse.value.right).toEqual(")"); }); it("should error when it is mismatched", function() { const unmatchedLeft = "\\left( \\dfrac{x}{y}"; const unmatchedRight = "\\dfrac{x}{y} \\right)"; expect(unmatchedLeft).toNotParse(); expect(unmatchedRight).toNotParse(); }); it("should error when braces are mismatched", function() { const unmatched = "{ \\left( \\dfrac{x}{y} } \\right)"; expect(unmatched).toNotParse(); }); it("should error when non-delimiters are provided", function() { const nonDelimiter = "\\left$ \\dfrac{x}{y} \\right)"; expect(nonDelimiter).toNotParse(); }); it("should parse the empty '.' delimiter", function() { expect(emptyRight).toParse(); }); it("should parse the '.' delimiter with normal sizes", function() { const normalEmpty = "\\Bigl ."; expect(normalEmpty).toParse(); }); it("should handle \\middle", function() { const normalMiddle = "\\left( \\dfrac{x}{y} \\middle| \\dfrac{y}{z} \\right)"; expect(normalMiddle).toParse(); }); it("should handle multiple \\middles", function() { const multiMiddle = "\\left( \\dfrac{x}{y} \\middle| \\dfrac{y}{z} \\middle/ \\dfrac{z}{q} \\right)"; expect(multiMiddle).toParse(); }); it("should handle nested \\middles", function() { const nestedMiddle = "\\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 = "(\\middle|\\dfrac{x}{y})"; expect(unmatchedMiddle).toNotParse(); }); }); describe("left/right builder", () => { const cases = [ ['\\left\\langle \\right\\rangle', '\\left< \\right>'], ['\\left\\langle \\right\\rangle', '\\left\u27e8 \\right\u27e9'], ]; 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(); }); it("should error when name is mismatched", function() { expect("\\begin{matrix}a&b\\\\c&d\\end{pmatrix}").toNotParse(); }); it("should error when commands are mismatched", function() { expect("\\begin{matrix}a&b\\\\c&d\\right{pmatrix}").toNotParse(); }); it("should error when end is missing", function() { expect("\\begin{matrix}a&b\\\\c&d").toNotParse(); }); it("should error when braces are mismatched", function() { expect("{\\begin{matrix}a&b\\\\c&d}\\end{matrix}").toNotParse(); }); 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 = "\\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.value.body.length).toBe(2); }); }); describe("A sqrt parser", function() { const sqrt = "\\sqrt{x}"; const missingGroup = "\\sqrt"; it("should parse square roots", function() { expect(sqrt).toParse(); }); it("should error when there is no group", function() { expect(missingGroup).toNotParse(); }); it("should produce sqrts", function() { const parse = getParsed(sqrt)[0]; expect(parse.type).toEqual("sqrt"); }); }); 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 = [ "\\frac{x}", "\\textcolor{#fff}", "\\rule{1em}", "\\llap", "\\bigl", "\\text", ]; for (let i = 0; i < missingGroups.length; i++) { expect(missingGroups[i]).toNotParse(); } }); it("should fail when there are missing sup/subscripts", function() { expect("x^").toNotParse(); expect("x_").toNotParse(); }); it("should fail when arguments require arguments", function() { const badArguments = [ "\\frac \\frac x y z", "\\frac x \\frac y z", "\\frac \\sqrt x y", "\\frac x \\sqrt y", "\\frac \\mathllap x y", "\\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 // "\\llap \\frac x y", "\\mathllap \\mathllap x", "\\sqrt \\mathllap x", ]; for (let i = 0; i < badArguments.length; i++) { expect(badArguments[i]).toNotParse(); } }); it("should work when the arguments have braces", function() { const goodArguments = [ "\\frac {\\frac x y} z", "\\frac x {\\frac y z}", "\\frac {\\sqrt x} y", "\\frac x {\\sqrt y}", "\\frac {\\mathllap x} y", "\\frac x {\\mathllap y}", "\\mathllap {\\frac x y}", "\\mathllap {\\mathllap x}", "\\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 = [ "x^\\sqrt x", "x^\\mathllap x", "x_\\sqrt x", "x_\\mathllap x", ]; for (let i = 0; i < badSupSubscripts.length; i++) { expect(badSupSubscripts[i]).toNotParse(); } }); it("should work when sup/subscripts arguments have braces", function() { const goodSupSubscripts = [ "x^{\\sqrt x}", "x^{\\mathllap x}", "x_{\\sqrt x}", "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").toNotParse(); expect("\\frac^234").toNotParse(); expect("\\frac2^34").toNotParse(); }); 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 = [ "\\frac \\left( x \\right) y", "\\frac x \\left( y \\right)", "\\mathllap \\left( x \\right)", "\\sqrt \\left( x \\right)", "x^\\left( x \\right)", ]; for (let i = 0; i < badLeftArguments.length; i++) { expect(badLeftArguments[i]).toNotParse(); } }); it("should succeed when there are braces around the \\left/\\right", function() { const goodLeftArguments = [ "\\frac {\\left( x \\right)} y", "\\frac x {\\left( y \\right)}", "\\mathllap {\\left( x \\right)}", "\\sqrt {\\left( x \\right)}", "x^{\\left( x \\right)}", ]; for (let i = 0; i < goodLeftArguments.length; i++) { expect(goodLeftArguments[i]).toParse(); } }); }); 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.value.style).toEqual("display"); const scriptscriptParse = getParsed("\\scriptscriptstyle x")[0]; expect(scriptscriptParse.value.style).toEqual("scriptscript"); }); it("should only change the style within its group", function() { const text = "a b { c d \\displaystyle e f } g h"; const parse = getParsed(text); const displayNode = parse[2].value[2]; expect(displayNode.type).toEqual("styling"); const displayBody = displayNode.value.value; expect(displayBody.length).toEqual(2); expect(displayBody[0].value).toEqual("e"); }); }); describe("A font parser", function() { it("should parse \\mathrm, \\mathbb, and \\mathit", function() { expect("\\mathrm x").toParse(); expect("\\mathbb x").toParse(); expect("\\mathit x").toParse(); expect("\\mathrm {x + 1}").toParse(); expect("\\mathbb {x + 1}").toParse(); expect("\\mathit {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.value.font).toEqual("mathbb"); expect(mathbbParse.value.type).toEqual("font"); const mathrmParse = getParsed("\\mathrm x")[0]; expect(mathrmParse.value.font).toEqual("mathrm"); expect(mathrmParse.value.type).toEqual("font"); const mathitParse = getParsed("\\mathit x")[0]; expect(mathitParse.value.font).toEqual("mathit"); expect(mathitParse.value.type).toEqual("font"); const mathcalParse = getParsed("\\mathcal C")[0]; expect(mathcalParse.value.font).toEqual("mathcal"); expect(mathcalParse.value.type).toEqual("font"); const mathfrakParse = getParsed("\\mathfrak C")[0]; expect(mathfrakParse.value.font).toEqual("mathfrak"); expect(mathfrakParse.value.type).toEqual("font"); }); it("should parse nested font commands", function() { const nestedParse = getParsed("\\mathbb{R \\neq \\mathrm{R}}")[0]; expect(nestedParse.value.font).toEqual("mathbb"); expect(nestedParse.value.type).toEqual("font"); expect(nestedParse.value.body.value.length).toEqual(3); const bbBody = nestedParse.value.body.value; expect(bbBody[0].type).toEqual("mathord"); expect(bbBody[1].type).toEqual("rel"); expect(bbBody[2].type).toEqual("font"); expect(bbBody[2].value.font).toEqual("mathrm"); expect(bbBody[2].value.type).toEqual("font"); }); it("should work with \\textcolor", function() { const colorMathbbParse = getParsed("\\textcolor{blue}{\\mathbb R}")[0]; expect(colorMathbbParse.value.type).toEqual("color"); expect(colorMathbbParse.value.color).toEqual("blue"); const body = colorMathbbParse.value.value; expect(body.length).toEqual(1); expect(body[0].value.type).toEqual("font"); expect(body[0].value.font).toEqual("mathbb"); }); it("should not parse a series of font commands", function() { expect("\\mathbb \\mathrm R").toNotParse(); }); it("should nest fonts correctly", function() { const bf = getParsed("\\mathbf{a\\mathrm{b}c}")[0]; expect(bf.value.type).toEqual("font"); expect(bf.value.font).toEqual("mathbf"); expect(bf.value.body.value.length).toEqual(3); expect(bf.value.body.value[0].value).toEqual("a"); expect(bf.value.body.value[1].value.type).toEqual("font"); expect(bf.value.body.value[1].value.font).toEqual("mathrm"); expect(bf.value.body.value[2].value).toEqual("c"); }); it("should have the correct greediness", function() { expect("e^\\mathbf{x}").toParse(); }); }); 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 not parse a comment that isn't followed by a newline", () => { expect("x%y").toNotParse(); }); 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("\\mathbb{R}"); expect(markup).toContain("R"); }); it("should render \\mathrm{R} with the correct font", function() { const markup = katex.renderToString("\\mathrm{R}"); expect(markup).toContain("R"); }); it("should render \\mathcal{R} with the correct font", function() { const markup = katex.renderToString("\\mathcal{R}"); expect(markup).toContain("R"); }); it("should render \\mathfrak{R} with the correct font", function() { const markup = katex.renderToString("\\mathfrak{R}"); expect(markup).toContain("R"); }); it("should render \\text{R} with the correct font", function() { const markup = katex.renderToString("\\text{R}"); expect(markup).toContain("R"); }); it("should render \\textit{R} with the correct font", function() { const markup = katex.renderToString("\\textit{R}"); expect(markup).toContain("R"); }); it("should render \\text{\\textit{R}} with the correct font", function() { const markup = katex.renderToString("\\text{\\textit{R}}"); expect(markup).toContain("R"); }); it("should render \\text{R\\textit{S}T} with the correct fonts", function() { const markup = katex.renderToString("\\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("\\textbf{R}"); expect(markup).toContain("R"); }); it("should render \\textsf{R} with the correct font", function() { const markup = katex.renderToString("\\textsf{R}"); expect(markup).toContain("R"); }); it("should render \\textsf{\\textit{R}G\\textbf{B}} with the correct font", function() { const markup = katex.renderToString("\\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("\\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("\\textsf{\\textbf{$\\mathrm{\\textsf{A}}$}}"); expect(markup).toContain("A"); }); it("should render \\texttt{R} with the correct font", function() { const markup = katex.renderToString("\\texttt{R}"); expect(markup).toContain("R"); }); it("should render a combination of font and color changes", function() { let markup = katex.renderToString("\\textcolor{blue}{\\mathbb R}"); let span = "R"; expect(markup).toContain(span); markup = katex.renderToString("\\mathbb{\\textcolor{blue}{R}}"); span = "R"; expect(markup).toContain(span); }); 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("\\sqrt{123}"); }).not.toThrowError(TypeError); expect(function() { katex.renderToString(new String("\\sqrt{123}")); }).not.toThrowError(TypeError); }); }); describe("A MathML font tree-builder", function() { const contents = "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 \\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"); // script is caps only expect(markup).toContain("2"); // MathJax marks everything below as "script" except \omega // We don't have these glyphs in "caligraphic" and neither does MathJax 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"); // MathJax marks everything below as "fraktur" except \omega // We don't have these glyphs in "fraktur" and neither does MathJax 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"); // MathJax marks everything below as "script" except \omega // We don't have these glyphs in "script" and neither does MathJax 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 = "\\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 = "\\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 = "\\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 = "\\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"); }); }); 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 markup generator", function() { it("marks trees up", function() { // Just a few quick sanity checks here... const markup = katex.renderToString("\\sigma^2"); expect(markup.indexOf(""); }); }); 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("\\error", noThrowSettings); expect(parsedInput[0].type).toBe("color"); expect(parsedInput[0].value.color).toBe(errorColor); }); it("should build katex-error span for other type of KaTeX error", function() { // Use _getBuilt instead of getBuilt to avoid calling expect...toParse // and thus throwing parse error 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(getBuilt("<")).toEqual(getBuilt("\\lt")); expect(getBuilt(">")).toEqual(getBuilt("\\gt")); expect(getBuilt("\\left<\\frac{1}{x}\\right>")) .toEqual(getBuilt("\\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 = "\\yen\\checkmark\\circledR\\maltese"; expect(symbols).toBuild(); expect(`\\text{${symbols}}`).toBuild(); }); }); describe("A macro expander", function() { const compareParseTree = function(actual, expected, macros) { const settings = new Settings({macros: macros}); actual = stripPositions(parseTree(actual, settings)); expected = stripPositions(parseTree(expected, defaultSettings)); expect(actual).toEqual(expected); }; it("should produce individual tokens", function() { compareParseTree("e^\\foo", "e^1 23", {"\\foo": "123"}); }); it("should preserve leading spaces inside macro definition", function() { compareParseTree("\\text{\\foo}", "\\text{ x}", {"\\foo": " x"}); }); it("should preserve leading spaces inside macro argument", function() { compareParseTree("\\text{\\foo{ x}}", "\\text{ x}", {"\\foo": "#1"}); }); it("should ignore expanded spaces in math mode", function() { compareParseTree("\\foo", "x", {"\\foo": " x"}); }); it("should consume spaces after control-word macro", function() { compareParseTree("\\text{\\foo }", "\\text{x}", {"\\foo": "x"}); }); it("should consume spaces after macro with \\relax", function() { compareParseTree("\\text{\\foo }", "\\text{}", {"\\foo": "\\relax"}); }); it("should not consume spaces after control-word expansion", function() { compareParseTree("\\text{\\\\ }", "\\text{ }", {"\\\\": "\\relax"}); }); it("should consume spaces after \\relax", function() { compareParseTree("\\text{\\relax }", "\\text{}"); }); it("should consume spaces after control-word function", function() { compareParseTree("\\text{\\KaTeX }", "\\text{\\KaTeX}"); }); it("should preserve spaces after control-symbol macro", function() { compareParseTree("\\text{\\% y}", "\\text{x y}", {"\\%": "x"}); }); it("should preserve spaces after control-symbol function", function() { expect("\\text{\\' }").toParse(); }); it("should consume spaces between arguments", function() { compareParseTree("\\text{\\foo 1 2}", "\\text{12end}", {"\\foo": "#1#2end"}); compareParseTree("\\text{\\foo {1} {2}}", "\\text{12end}", {"\\foo": "#1#2end"}); }); it("should allow for multiple expansion", function() { compareParseTree("1\\foo2", "1aa2", { "\\foo": "\\bar\\bar", "\\bar": "a", }); }); it("should allow for multiple expansion with argument", function() { compareParseTree("1\\foo2", "12222", { "\\foo": "\\bar{#1}\\bar{#1}", "\\bar": "#1#1", }); }); it("should allow for macro argument", function() { compareParseTree("\\foo\\bar", "(x)", { "\\foo": "(#1)", "\\bar": "x", }); }); it("should allow for space macro argument (text version)", function() { compareParseTree("\\text{\\foo\\bar}", "\\text{( )}", { "\\foo": "(#1)", "\\bar": " ", }); }); it("should allow for space macro argument (math version)", function() { compareParseTree("\\foo\\bar", "()", { "\\foo": "(#1)", "\\bar": " ", }); }); it("should allow for space second argument (text version)", function() { compareParseTree("\\text{\\foo\\bar\\bar}", "\\text{( , )}", { "\\foo": "(#1,#2)", "\\bar": " ", }); }); it("should allow for space second argument (math version)", function() { compareParseTree("\\foo\\bar\\bar", "(,)", { "\\foo": "(#1,#2)", "\\bar": " ", }); }); it("should allow for empty macro argument", function() { compareParseTree("\\foo\\bar", "()", { "\\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() { compareParseTree("\\frac\\bar\\bar", "\\frac{}{}", { "\\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() { compareParseTree("x’=c", "x'=c", { "’": "'", }); }); 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() { compareParseTree("\\mode\\text{\\mode$\\mode$\\mode}\\mode", "math\\text{text$math$text}math", {"\\mode": "\\TextOrMath{text}{math}"}); }); it("\\TextOrMath should work in a macro passed to \\text", function() { compareParseTree("\\text\\mode", "\\text t", {"\\mode": "\\TextOrMath{t}{m}"}); }); // 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() { // compareParseTree("\\text\\mode", "\\text{text}", // {"\\mode": "\\TextOrMath{text}{math}"}); //}); // 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"); }); }); 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("\\div")).toParseLike("\\div"); expect(new String("\\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( "\\grave A\\acute A\\hat A\\tilde A\\ddot A\\mathring A" + "\\grave E\\acute E\\hat E\\ddot E" + "\\grave I\\acute I\\hat I\\ddot I" + "\\tilde N" + "\\grave O\\acute O\\hat O\\tilde O\\ddot O" + "\\grave U\\acute U\\hat U\\ddot U" + "\\acute Y" + "\\grave a\\acute a\\hat a\\tilde a\\ddot a\\mathring a" + "\\grave e\\acute e\\hat e\\ddot e" + "\\grave ı\\acute ı\\hat ı\\ddot ı" + "\\tilde n" + "\\grave o\\acute o\\hat o\\tilde o\\ddot o" + "\\grave u\\acute u\\hat u\\ddot u" + "\\acute y\\ddot y"); }); it("should parse Latin-1 letters in text mode", function() { // TODO(edemaine): Unsupported Latin-1 letters in text: ÇÐÞçðþ expect("\\text{ÀÁÂÃÄÅÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåèéêëìíîïñòóôõöùúûüýÿ}") .toParseLike( "\\text{\\`A\\'A\\^A\\~A\\\"A\\r A" + "\\`E\\'E\\^E\\\"E" + "\\`I\\'I\\^I\\\"I" + "\\~N" + "\\`O\\'O\\^O\\~O\\\"O" + "\\`U\\'U\\^U\\\"U" + "\\'Y" + "\\`a\\'a\\^a\\~a\\\"a\\r a" + "\\`e\\'e\\^e\\\"e" + "\\`ı\\'ı\\^ı\\\"ı" + "\\~n" + "\\`o\\'o\\^o\\~o\\\"o" + "\\`u\\'u\\^u\\\"u" + "\\'y\\\"y}"); }); it("should support \\aa in text mode", function() { expect("\\text{\\aa\\AA}").toParseLike("\\text{\\r a\\r A}"); expect("\\aa").toNotParse(new Settings({strict: true})); expect("\\Aa").toNotParse(new Settings({strict: true})); }); it("should parse combining characters", function() { expect("A\u0301C\u0301").toParseLike("Á\\acute C"); expect("\\text{A\u0301C\u0301}").toParseLike("\\text{Á\\'C}"); }); it("should parse multi-accented characters", function() { expect("ấā́ắ\\text{ấā́ắ}").toParse(); // 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("\\acute ı\\acute ȷ"); expect("ấā́ắ\\text{ấā́ắ}").toParse(); }); }); describe("Unicode", function() { it("should parse negated relations", function() { expect("∉∤∦≁≆≠≨≩≮≯≰≱⊀⊁⊈⊉⊊⊋⊬⊭⊮⊯⋠⋡⋦⋧⋨⋩⋬⋭⪇⪈⪉⪊⪵⪶⪹⪺⫋⫌").toParse(); }); it("should parse relations", function() { expect("∈∋∝∼∽≂≃≅≈≊≍≎≏≐≑≒≓≖≗≜≡≤≥≦≧≪≫≬≳≷≺≻≼≽≾≿∴∵∣≔≕⩴⋘⋙").toParse(); }); it("should parse big operators", function() { expect("∏∐∑∫∬∭∮⋀⋁⋂⋃⨀⨁⨂⨄⨆").toParse(); }); it("should parse more relations", function() { expect("⊂⊃⊆⊇⊏⊐⊑⊒⊢⊣⊩⊪⊸⋈⋍⋐⋑⋔⋛⋞⋟⌢⌣⩾⪆⪌⪕⪖⪯⪰⪷⪸⫅⫆≘≙≚≛≝≞≟").toBuild(); }); it("should parse symbols", function() { expect("£¥ðℂℍℑℓℕ℘ℙℚℜℝℤℲℵℶℷℸ⅁∀∁∂∃∇∞∠∡∢♠♡♢♣♭♮♯✓°\u00b7").toParse(); }); it("should build Greek capital letters", function() { expect("\u0391\u0392\u0395\u0396\u0397\u0399\u039A\u039C\u039D" + "\u039F\u03A1\u03A4\u03A7").toBuild(); }); it("should parse arrows", function() { expect("←↑→↓↔↕↖↗↘↙↚↛↞↠↢↣↦↩↪↫↬↭↮↰↱↶↷↼↽↾↾↿⇀⇁⇂⇃⇄⇆⇇⇈⇉").toParse(); }); it("should parse more arrows", function() { expect("⇊⇋⇌⇍⇎⇏⇐⇑⇒⇓⇔⇕⇚⇛⇝⟵⟶⟷⟸⟹⟺⟼").toParse(); }); it("should parse binary operators", function() { expect("±×÷∓∔∧∨∩∪≀⊎⊓⊔⊕⊖⊗⊘⊙⊚⊛⊝⊞⊟⊠⊡⊺⊻⊼⋇⋉⋊⋋⋌⋎⋏⋒⋓⩞\u22C5").toParse(); }); 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(); }); 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(); 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(); }); }); describe("The maxSize setting", function() { const rule = "\\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 \\mathchoice function", function() { const cmd = "\\sum_{k = 0}^{\\infty} x^k"; it("should render as if there is nothing other in display math", function() { const plain = getBuilt("\\displaystyle" + cmd)[0]; const built = getBuilt(`\\displaystyle\\mathchoice{${cmd}}{T}{S}{SS}`)[0]; expect(built).toEqual(plain); }); it("should render as if there is nothing other in text", function() { const plain = getBuilt(cmd)[0]; const built = getBuilt(`\\mathchoice{D}{${cmd}}{S}{SS}`)[0]; expect(built).toEqual(plain); }); it("should render as if there is nothing other in scriptstyle", function() { const plain = getBuilt(`x_{${cmd}}`)[0]; const built = getBuilt(`x_{\\mathchoice{D}{T}{${cmd}}{SS}}`)[0]; expect(built).toEqual(plain); }); it("should render as if there is nothing other in scriptscriptstyle", function() { const plain = getBuilt(`x_{y_{${cmd}}}`)[0]; const built = getBuilt(`x_{y_{\\mathchoice{D}{T}{S}{${cmd}}}}`)[0]; expect(built).toEqual(plain); }); }); 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").toNotBuild(); }); }); describe("Symbols", function() { it("should parse \\text{\\i\\j}", () => { expect("\\text{\\i\\j}").toBuild(); }); it("should parse spacing functions in math or text mode", () => { expect("A\\;B\\,C\\nobreakspace \\text{A\\;B\\,C\\nobreakspace}").toBuild(); }); it("should render ligature commands like their unicode characters", () => { const commands = getBuilt("\\text{\\ae\\AE\\oe\\OE\\o\\O\\ss}"); const unicode = getBuilt("\\text{æÆœŒøØß}"); expect(commands).toEqual(unicode); }); }); describe("strict setting", function() { it("should allow unicode text when not strict", () => { expect("é").toParse(new Settings({strict: false})); expect("試").toParse(new Settings({strict: false})); 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("é").toNotParse(new Settings({strict: true})); expect("試").toNotParse(new Settings({strict: true})); expect("é").toNotParse(new Settings({strict: "error"})); expect("試").toNotParse(new Settings({strict: "error"})); expect("é").toNotParse(new Settings({strict: () => true})); expect("試").toNotParse(new Settings({strict: () => true})); expect("é").toNotParse(new Settings({strict: () => "error"})); expect("試").toNotParse(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(new Settings({strict: false})); expect("\\text{é試}").toParse(new Settings({strict: true})); 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 = "\\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); }); });