
Summary: The greediness of the `\color` function wasn't set correctly, leading to expressions like `\color{red}\text{a}` parsing correctly, when they shouldn't. (This is based on how MathJax parses, since TeX doesn't have a `\color` function, so MathJax is the standard). Test Plan: - Make test - See that `\color{red}\text{a}` doesn't parse (like MathJax) - See that `\color{red}{\text{a}}` does parse (like MathJax) - See that `\color{red}\frac12` doesn't parse (like MathJax) - See that `\color{red}{\frac12}` does parse (like MathJax) - See that `\red\text{a}` doesn't parse (like MathJax) - See that `\red{\text{a}}` does parse (like MathJax) - See that `\red\frac12` doesn't parse (like MathJax) - See that `\red{\frac12}` does parse (like MathJax) Reviewers: alpert Reviewed By: alpert Differential Revision: https://phabricator.khanacademy.org/D17130
1272 lines
38 KiB
JavaScript
1272 lines
38 KiB
JavaScript
var buildHTML = require("../src/buildHTML");
|
|
var buildMathML = require("../src/buildMathML");
|
|
var katex = require("../katex");
|
|
var ParseError = require("../src/ParseError");
|
|
var parseTree = require("../src/parseTree");
|
|
var Settings = require("../src/Settings");
|
|
|
|
var defaultSettings = new Settings({});
|
|
|
|
var getBuilt = function(expr) {
|
|
expect(expr).toBuild();
|
|
|
|
var built = buildHTML(parseTree(expr), defaultSettings);
|
|
|
|
// Remove the outer .katex and .katex-inner layers
|
|
return built.children[2].children;
|
|
};
|
|
|
|
var getParsed = function(expr) {
|
|
expect(expr).toParse();
|
|
|
|
return parseTree(expr, defaultSettings);
|
|
};
|
|
|
|
beforeEach(function() {
|
|
jasmine.addMatchers({
|
|
toParse: function() {
|
|
return {
|
|
compare: function(actual) {
|
|
var result = {
|
|
pass: true,
|
|
message: "'" + actual + "' succeeded parsing"
|
|
};
|
|
|
|
try {
|
|
parseTree(actual, defaultSettings);
|
|
} catch (e) {
|
|
result.pass = false;
|
|
if (e instanceof ParseError) {
|
|
result.message = "'" + actual + "' failed " +
|
|
"parsing with error: " + e.message;
|
|
} else {
|
|
result.message = "'" + actual + "' failed " +
|
|
"parsing with unknown error: " + e.message;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
};
|
|
},
|
|
|
|
toNotParse: function() {
|
|
return {
|
|
compare: function(actual) {
|
|
var result = {
|
|
pass: false,
|
|
message: "Expected '" + actual + "' to fail " +
|
|
"parsing, but it succeeded"
|
|
};
|
|
|
|
try {
|
|
parseTree(actual, defaultSettings);
|
|
} catch (e) {
|
|
if (e instanceof ParseError) {
|
|
result.pass = true;
|
|
result.message = "'" + actual + "' correctly " +
|
|
"didn't parse with error: " + e.message;
|
|
} else {
|
|
result.message = "'" + actual + "' failed " +
|
|
"parsing with unknown error: " + e.message;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
};
|
|
},
|
|
|
|
toBuild: function() {
|
|
return {
|
|
compare: function(actual) {
|
|
var result = {
|
|
pass: true,
|
|
message: "'" + actual + "' succeeded in building"
|
|
};
|
|
|
|
expect(actual).toParse();
|
|
|
|
try {
|
|
buildHTML(parseTree(actual), defaultSettings);
|
|
} catch (e) {
|
|
result.pass = false;
|
|
if (e instanceof ParseError) {
|
|
result.message = "'" + actual + "' failed to " +
|
|
"build with error: " + e.message;
|
|
} else {
|
|
result.message = "'" + actual + "' failed " +
|
|
"building with unknown error: " + e.message;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
};
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("A parser", function() {
|
|
it("should not fail on an empty string", function() {
|
|
expect("").toParse();
|
|
});
|
|
|
|
it("should ignore whitespace", function() {
|
|
var parseA = getParsed(" x y ");
|
|
var parseB = getParsed("xy");
|
|
expect(parseA).toEqual(parseB);
|
|
});
|
|
});
|
|
|
|
describe("An ord parser", function() {
|
|
var expression = "1234|/@.\"`abcdefgzABCDEFGZ";
|
|
|
|
it("should not fail", function() {
|
|
expect(expression).toParse();
|
|
});
|
|
|
|
it("should build a list of ords", function() {
|
|
var parse = getParsed(expression);
|
|
|
|
expect(parse).toBeTruthy();
|
|
|
|
for (var i = 0; i < parse.length; i++) {
|
|
var group = parse[i];
|
|
expect(group.type).toMatch("ord");
|
|
}
|
|
});
|
|
|
|
it("should parse the right number of ords", function() {
|
|
var parse = getParsed(expression);
|
|
|
|
expect(parse.length).toBe(expression.length);
|
|
});
|
|
});
|
|
|
|
describe("A bin parser", function() {
|
|
var expression = "+-*\\cdot\\pm\\div";
|
|
|
|
it("should not fail", function() {
|
|
expect(expression).toParse();
|
|
});
|
|
|
|
it("should build a list of bins", function() {
|
|
var parse = getParsed(expression);
|
|
expect(parse).toBeTruthy();
|
|
|
|
for (var i = 0; i < parse.length; i++) {
|
|
var group = parse[i];
|
|
expect(group.type).toMatch("bin");
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("A rel parser", function() {
|
|
var expression = "=<>\\leq\\geq\\neq\\nleq\\ngeq\\cong";
|
|
|
|
it("should not fail", function() {
|
|
expect(expression).toParse();
|
|
});
|
|
|
|
it("should build a list of rels", function() {
|
|
var parse = getParsed(expression);
|
|
expect(parse).toBeTruthy();
|
|
|
|
for (var i = 0; i < parse.length; i++) {
|
|
var group = parse[i];
|
|
expect(group.type).toMatch("rel");
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("A punct parser", function() {
|
|
var expression = ",;\\colon";
|
|
|
|
it("should not fail", function() {
|
|
expect(expression).toParse();
|
|
});
|
|
|
|
it("should build a list of puncts", function() {
|
|
var parse = getParsed(expression);
|
|
expect(parse).toBeTruthy();
|
|
|
|
for (var i = 0; i < parse.length; i++) {
|
|
var group = parse[i];
|
|
expect(group.type).toMatch("punct");
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("An open parser", function() {
|
|
var expression = "([";
|
|
|
|
it("should not fail", function() {
|
|
expect(expression).toParse();
|
|
});
|
|
|
|
it("should build a list of opens", function() {
|
|
var parse = getParsed(expression);
|
|
expect(parse).toBeTruthy();
|
|
|
|
for (var i = 0; i < parse.length; i++) {
|
|
var group = parse[i];
|
|
expect(group.type).toMatch("open");
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("A close parser", function() {
|
|
var expression = ")]?!";
|
|
|
|
it("should not fail", function() {
|
|
expect(expression).toParse();
|
|
});
|
|
|
|
it("should build a list of closes", function() {
|
|
var parse = getParsed(expression);
|
|
expect(parse).toBeTruthy();
|
|
|
|
for (var i = 0; i < parse.length; i++) {
|
|
var group = parse[i];
|
|
expect(group.type).toMatch("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("_2").toParse();
|
|
expect("^3_2").toParse();
|
|
expect("_2^3").toParse();
|
|
});
|
|
|
|
it("should produce supsubs for superscript", function() {
|
|
var 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() {
|
|
var 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() {
|
|
var 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() {
|
|
var 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() {
|
|
var parseA = getParsed("x^2_3");
|
|
var parseB = 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 group parser", function() {
|
|
it("should not fail", function() {
|
|
expect("{xy}").toParse();
|
|
});
|
|
|
|
it("should produce a single ord", function() {
|
|
var parse = getParsed("{xy}");
|
|
|
|
expect(parse.length).toBe(1);
|
|
|
|
var 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() {
|
|
var parse = getParsed("\\Large abc");
|
|
|
|
expect(parse.length).toBe(1);
|
|
|
|
var sizing = parse[0];
|
|
|
|
expect(sizing.type).toMatch("sizing");
|
|
expect(sizing.value).toBeTruthy();
|
|
});
|
|
|
|
it("should apply only after the function", function() {
|
|
var parse = getParsed("a \\Large abc");
|
|
|
|
expect(parse.length).toBe(2);
|
|
|
|
var sizing = parse[1];
|
|
|
|
expect(sizing.type).toMatch("sizing");
|
|
expect(sizing.value.value.length).toBe(3);
|
|
});
|
|
|
|
it("should stop at the ends of groups", function() {
|
|
var parse = getParsed("a { b \\Large c } d");
|
|
|
|
var group = parse[1];
|
|
var sizing = group.value[1];
|
|
|
|
expect(sizing.type).toMatch("sizing");
|
|
expect(sizing.value.value.length).toBe(1);
|
|
});
|
|
});
|
|
|
|
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() {
|
|
var expression = "\\frac{x}{y}";
|
|
var dfracExpression = "\\dfrac{x}{y}";
|
|
var tfracExpression = "\\tfrac{x}{y}";
|
|
|
|
it("should not fail", function() {
|
|
expect(expression).toParse();
|
|
});
|
|
|
|
it("should produce a frac", function() {
|
|
var parse = getParsed(expression)[0];
|
|
|
|
expect(parse.type).toMatch("frac");
|
|
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() {
|
|
var dfracParse = getParsed(dfracExpression)[0];
|
|
|
|
expect(dfracParse.type).toMatch("frac");
|
|
expect(dfracParse.value.numer).toBeDefined();
|
|
expect(dfracParse.value.denom).toBeDefined();
|
|
|
|
var tfracParse = getParsed(tfracExpression)[0];
|
|
|
|
expect(tfracParse.type).toMatch("frac");
|
|
expect(tfracParse.value.numer).toBeDefined();
|
|
expect(tfracParse.value.denom).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe("An over parser", function() {
|
|
var simpleOver = "1 \\over x";
|
|
var complexOver = "1+2i \\over 3+4i";
|
|
|
|
it("should not fail", function () {
|
|
expect(simpleOver).toParse();
|
|
expect(complexOver).toParse();
|
|
});
|
|
|
|
it("should produce a frac", function() {
|
|
var parse;
|
|
|
|
parse = getParsed(simpleOver)[0];
|
|
|
|
expect(parse.type).toMatch("frac");
|
|
expect(parse.value.numer).toBeDefined();
|
|
expect(parse.value.denom).toBeDefined();
|
|
|
|
parse = getParsed(complexOver)[0];
|
|
|
|
expect(parse.type).toMatch("frac");
|
|
expect(parse.value.numer).toBeDefined();
|
|
expect(parse.value.denom).toBeDefined();
|
|
});
|
|
|
|
it("should create a numerator from the atoms before \\over", function () {
|
|
var parse = getParsed(complexOver)[0];
|
|
|
|
var numer = parse.value.numer;
|
|
expect(numer.value.length).toEqual(4);
|
|
});
|
|
|
|
it("should create a demonimator from the atoms after \\over", function () {
|
|
var parse = getParsed(complexOver)[0];
|
|
|
|
var denom = parse.value.numer;
|
|
expect(denom.value.length).toEqual(4);
|
|
});
|
|
|
|
it("should handle empty numerators", function () {
|
|
var emptyNumerator = "\\over x";
|
|
var parse = getParsed(emptyNumerator)[0];
|
|
expect(parse.type).toMatch("frac");
|
|
expect(parse.value.numer).toBeDefined();
|
|
expect(parse.value.denom).toBeDefined();
|
|
});
|
|
|
|
it("should handle empty denominators", function () {
|
|
var emptyDenominator = "1 \\over";
|
|
var parse = getParsed(emptyDenominator)[0];
|
|
expect(parse.type).toMatch("frac");
|
|
expect(parse.value.numer).toBeDefined();
|
|
expect(parse.value.denom).toBeDefined();
|
|
});
|
|
|
|
it("should handle \\displaystyle correctly", function () {
|
|
var displaystyleExpression = "\\displaystyle 1 \\over 2";
|
|
var parse = getParsed(displaystyleExpression)[0];
|
|
expect(parse.type).toMatch("frac");
|
|
expect(parse.value.numer.value[0].type).toMatch("styling");
|
|
expect(parse.value.denom).toBeDefined();
|
|
});
|
|
|
|
it("should handle nested factions", function () {
|
|
var nestedOverExpression = "{1 \\over 2} \\over 3";
|
|
var parse = getParsed(nestedOverExpression)[0];
|
|
expect(parse.type).toMatch("frac");
|
|
expect(parse.value.numer.value[0].type).toMatch("frac");
|
|
expect(parse.value.numer.value[0].value.numer.value[0].value).toMatch(1);
|
|
expect(parse.value.numer.value[0].value.denom.value[0].value).toMatch(2);
|
|
expect(parse.value.denom).toBeDefined();
|
|
expect(parse.value.denom.value[0].value).toMatch(3);
|
|
});
|
|
|
|
it("should fail with multiple overs in the same group", function () {
|
|
var badMultipleOvers = "1 \\over 2 + 3 \\over 4";
|
|
expect(badMultipleOvers).toNotParse();
|
|
|
|
var badOverChoose = "1 \\over 2 \\choose 3";
|
|
expect(badOverChoose).toNotParse();
|
|
});
|
|
});
|
|
|
|
describe("A sizing parser", function() {
|
|
var sizeExpression = "\\Huge{x}\\small{x}";
|
|
|
|
it("should not fail", function() {
|
|
expect(sizeExpression).toParse();
|
|
});
|
|
|
|
it("should produce a sizing node", function() {
|
|
var parse = getParsed(sizeExpression)[0];
|
|
|
|
expect(parse.type).toMatch("sizing");
|
|
expect(parse.value).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe("A text parser", function() {
|
|
var textExpression = "\\text{a b}";
|
|
var noBraceTextExpression = "\\text x";
|
|
var nestedTextExpression =
|
|
"\\text{a {b} \\blue{c} \\color{#fff}{x} \\llap{x}}";
|
|
var spaceTextExpression = "\\text{ a \\ }";
|
|
var leadingSpaceTextExpression = "\\text {moo}";
|
|
var badTextExpression = "\\text{a b%}";
|
|
var badFunctionExpression = "\\text{\\sqrt{x}}";
|
|
|
|
it("should not fail", function() {
|
|
expect(textExpression).toParse();
|
|
});
|
|
|
|
it("should produce a text", function() {
|
|
var parse = getParsed(textExpression)[0];
|
|
|
|
expect(parse.type).toMatch("text");
|
|
expect(parse.value).toBeDefined();
|
|
});
|
|
|
|
it("should produce textords instead of mathords", function() {
|
|
var parse = getParsed(textExpression)[0];
|
|
var group = parse.value.body;
|
|
|
|
expect(group[0].type).toMatch("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() {
|
|
var parse = getParsed(spaceTextExpression)[0];
|
|
var group = parse.value.body;
|
|
|
|
expect(group[0].type).toMatch("spacing");
|
|
expect(group[1].type).toMatch("textord");
|
|
expect(group[2].type).toMatch("spacing");
|
|
expect(group[3].type).toMatch("spacing");
|
|
});
|
|
|
|
it("should ignore a space before the text group", function() {
|
|
var 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");
|
|
});
|
|
});
|
|
|
|
describe("A color parser", function() {
|
|
var colorExpression = "\\blue{x}";
|
|
var customColorExpression = "\\color{#fA6}{x}";
|
|
var badCustomColorExpression = "\\color{bad-color}{x}";
|
|
|
|
it("should not fail", function() {
|
|
expect(colorExpression).toParse();
|
|
});
|
|
|
|
it("should build a color node", function() {
|
|
var parse = getParsed(colorExpression)[0];
|
|
|
|
expect(parse.type).toMatch("color");
|
|
expect(parse.value.color).toBeDefined();
|
|
expect(parse.value.value).toBeDefined();
|
|
});
|
|
|
|
it("should parse a custom color", function() {
|
|
expect(customColorExpression).toParse();
|
|
});
|
|
|
|
it("should correctly extract the custom color", function() {
|
|
var parse = getParsed(customColorExpression)[0];
|
|
|
|
expect(parse.value.color).toMatch("#fA6");
|
|
});
|
|
|
|
it("should not parse a bad custom color", function() {
|
|
expect(badCustomColorExpression).toNotParse();
|
|
});
|
|
|
|
it("should have correct greediness", function() {
|
|
expect("\\color{red}a").toParse();
|
|
expect("\\color{red}{\\text{a}}").toParse();
|
|
expect("\\color{red}\\text{a}").toNotParse();
|
|
expect("\\color{red}\\frac12").toNotParse();
|
|
});
|
|
});
|
|
|
|
describe("A tie parser", function() {
|
|
var mathTie = "a~b";
|
|
var 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() {
|
|
var parse = getParsed(mathTie);
|
|
|
|
expect(parse[1].type).toMatch("spacing");
|
|
});
|
|
|
|
it("should produce spacing in text mode", function() {
|
|
var text = getParsed(textTie)[0];
|
|
var parse = text.value.body;
|
|
|
|
expect(parse[1].type).toMatch("spacing");
|
|
});
|
|
|
|
it("should not contract with spaces in text mode", function() {
|
|
var text = getParsed(textTie)[0];
|
|
var parse = text.value.body;
|
|
|
|
expect(parse[2].type).toMatch("spacing");
|
|
});
|
|
});
|
|
|
|
describe("A delimiter sizing parser", function() {
|
|
var normalDelim = "\\bigl |";
|
|
var notDelim = "\\bigl x";
|
|
var 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() {
|
|
var parse = getParsed(normalDelim)[0];
|
|
|
|
expect(parse.type).toMatch("delimsizing");
|
|
});
|
|
|
|
it("should produce the correct direction delimiter", function() {
|
|
var leftParse = getParsed(normalDelim)[0];
|
|
var rightParse = getParsed(bigDelim)[0];
|
|
|
|
expect(leftParse.value.delimType).toMatch("open");
|
|
expect(rightParse.value.delimType).toMatch("close");
|
|
});
|
|
|
|
it("should parse the correct size delimiter", function() {
|
|
var smallParse = getParsed(normalDelim)[0];
|
|
var bigParse = getParsed(bigDelim)[0];
|
|
|
|
expect(smallParse.value.size).toEqual(1);
|
|
expect(bigParse.value.size).toEqual(4);
|
|
});
|
|
});
|
|
|
|
describe("An overline parser", function() {
|
|
var overline = "\\overline{x}";
|
|
|
|
it("should not fail", function() {
|
|
expect(overline).toParse();
|
|
});
|
|
|
|
it("should produce an overline", function() {
|
|
var parse = getParsed(overline)[0];
|
|
|
|
expect(parse.type).toMatch("overline");
|
|
});
|
|
});
|
|
|
|
describe("A rule parser", function() {
|
|
var emRule = "\\rule{1em}{2em}";
|
|
var exRule = "\\rule{1ex}{2em}";
|
|
var badUnitRule = "\\rule{1px}{2em}";
|
|
var noNumberRule = "\\rule{1em}{em}";
|
|
var incompleteRule = "\\rule{1em}";
|
|
var 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() {
|
|
var parse = getParsed(emRule)[0];
|
|
|
|
expect(parse.type).toMatch("rule");
|
|
});
|
|
|
|
it("should list the correct units", function() {
|
|
var emParse = getParsed(emRule)[0];
|
|
var exParse = getParsed(exRule)[0];
|
|
|
|
expect(emParse.value.width.unit).toMatch("em");
|
|
expect(emParse.value.height.unit).toMatch("em");
|
|
|
|
expect(exParse.value.width.unit).toMatch("ex");
|
|
expect(exParse.value.height.unit).toMatch("em");
|
|
});
|
|
|
|
it("should parse the number correctly", function() {
|
|
var 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() {
|
|
var parse = getParsed("\\rule{-1em}{- 0.2em}")[0];
|
|
|
|
expect(parse.value.width.number).toBeCloseTo(-1);
|
|
expect(parse.value.height.number).toBeCloseTo(-0.2);
|
|
});
|
|
});
|
|
|
|
describe("A left/right parser", function() {
|
|
var normalLeftRight = "\\left( \\dfrac{x}{y} \\right)";
|
|
var emptyRight = "\\left( \\dfrac{x}{y} \\right.";
|
|
|
|
it("should not fail", function() {
|
|
expect(normalLeftRight).toParse();
|
|
});
|
|
|
|
it("should produce a leftright", function() {
|
|
var parse = getParsed(normalLeftRight)[0];
|
|
|
|
expect(parse.type).toMatch("leftright");
|
|
expect(parse.value.left).toMatch("\\(");
|
|
expect(parse.value.right).toMatch("\\)");
|
|
});
|
|
|
|
it("should error when it is mismatched", function() {
|
|
var unmatchedLeft = "\\left( \\dfrac{x}{y}";
|
|
var unmatchedRight = "\\dfrac{x}{y} \\right)";
|
|
|
|
expect(unmatchedLeft).toNotParse();
|
|
|
|
expect(unmatchedRight).toNotParse();
|
|
});
|
|
|
|
it("should error when braces are mismatched", function() {
|
|
var unmatched = "{ \\left( \\dfrac{x}{y} } \\right)";
|
|
expect(unmatched).toNotParse();
|
|
});
|
|
|
|
it("should error when non-delimiters are provided", function() {
|
|
var 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() {
|
|
var normalEmpty = "\\Bigl .";
|
|
expect(normalEmpty).toParse();
|
|
});
|
|
});
|
|
|
|
describe("A sqrt parser", function() {
|
|
var sqrt = "\\sqrt{x}";
|
|
var 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() {
|
|
var parse = getParsed(sqrt)[0];
|
|
|
|
expect(parse.type).toMatch("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() {
|
|
var missingGroups = [
|
|
"\\frac{x}",
|
|
"\\color{#fff}",
|
|
"\\rule{1em}",
|
|
"\\llap",
|
|
"\\bigl",
|
|
"\\text"
|
|
];
|
|
|
|
for (var 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() {
|
|
var badArguments = [
|
|
"\\frac \\frac x y z",
|
|
"\\frac x \\frac y z",
|
|
"\\frac \\sqrt x y",
|
|
"\\frac x \\sqrt y",
|
|
"\\frac \\llap x y",
|
|
"\\frac x \\llap 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",
|
|
"\\llap \\llap x",
|
|
"\\sqrt \\llap x"
|
|
];
|
|
|
|
for (var i = 0; i < badArguments.length; i++) {
|
|
expect(badArguments[i]).toNotParse();
|
|
}
|
|
});
|
|
|
|
it("should work when the arguments have braces", function() {
|
|
var goodArguments = [
|
|
"\\frac {\\frac x y} z",
|
|
"\\frac x {\\frac y z}",
|
|
"\\frac {\\sqrt x} y",
|
|
"\\frac x {\\sqrt y}",
|
|
"\\frac {\\llap x} y",
|
|
"\\frac x {\\llap y}",
|
|
"\\llap {\\frac x y}",
|
|
"\\llap {\\llap x}",
|
|
"\\sqrt {\\llap x}"
|
|
];
|
|
|
|
for (var i = 0; i < goodArguments.length; i++) {
|
|
expect(goodArguments[i]).toParse();
|
|
}
|
|
});
|
|
|
|
it("should fail when sup/subscripts require arguments", function() {
|
|
var badSupSubscripts = [
|
|
"x^\\sqrt x",
|
|
"x^\\llap x",
|
|
"x_\\sqrt x",
|
|
"x_\\llap x"
|
|
];
|
|
|
|
for (var i = 0; i < badSupSubscripts.length; i++) {
|
|
expect(badSupSubscripts[i]).toNotParse();
|
|
}
|
|
});
|
|
|
|
it("should work when sup/subscripts arguments have braces", function() {
|
|
var goodSupSubscripts = [
|
|
"x^{\\sqrt x}",
|
|
"x^{\\llap x}",
|
|
"x_{\\sqrt x}",
|
|
"x_{\\llap x}"
|
|
];
|
|
|
|
for (var 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();
|
|
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() {
|
|
var badLeftArguments = [
|
|
"\\frac \\left( x \\right) y",
|
|
"\\frac x \\left( y \\right)",
|
|
"\\llap \\left( x \\right)",
|
|
"\\sqrt \\left( x \\right)",
|
|
"x^\\left( x \\right)"
|
|
];
|
|
|
|
for (var i = 0; i < badLeftArguments.length; i++) {
|
|
expect(badLeftArguments[i]).toNotParse();
|
|
}
|
|
});
|
|
|
|
it("should succeed when there are braces around the \\left/\\right", function() {
|
|
var goodLeftArguments = [
|
|
"\\frac {\\left( x \\right)} y",
|
|
"\\frac x {\\left( y \\right)}",
|
|
"\\llap {\\left( x \\right)}",
|
|
"\\sqrt {\\left( x \\right)}",
|
|
"x^{\\left( x \\right)}"
|
|
];
|
|
|
|
for (var 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() {
|
|
var displayParse = getParsed("\\displaystyle x")[0];
|
|
expect(displayParse.value.style).toMatch("display");
|
|
|
|
var scriptscriptParse = getParsed("\\scriptscriptstyle x")[0];
|
|
expect(scriptscriptParse.value.style).toMatch("scriptscript");
|
|
});
|
|
|
|
it("should only change the style within its group", function() {
|
|
var text = "a b { c d \\displaystyle e f } g h";
|
|
var parse = getParsed(text);
|
|
|
|
var displayNode = parse[2].value[2];
|
|
|
|
expect(displayNode.type).toMatch("styling");
|
|
|
|
var displayBody = displayNode.value.value;
|
|
|
|
expect(displayBody.length).toEqual(2);
|
|
expect(displayBody[0].value).toMatch("e");
|
|
});
|
|
});
|
|
|
|
describe("A bin builder", function() {
|
|
it("should create mbins normally", function() {
|
|
var built = getBuilt("x + y");
|
|
|
|
expect(built[1].classes).toContain("mbin");
|
|
});
|
|
|
|
it("should create ords when at the beginning of lists", function() {
|
|
var 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")[2].classes).toContain("mord");
|
|
expect(getBuilt("( + 2")[1].classes).toContain("mord");
|
|
expect(getBuilt("= + 2")[1].classes).toContain("mord");
|
|
expect(getBuilt("\\sin + 2")[1].classes).toContain("mord");
|
|
expect(getBuilt(", + 2")[1].classes).toContain("mord");
|
|
});
|
|
|
|
it("should correctly interact with color objects", function() {
|
|
expect(getBuilt("\\blue{x}+y")[1].classes).toContain("mbin");
|
|
expect(getBuilt("\\blue{x+}+y")[1].classes).toContain("mord");
|
|
});
|
|
});
|
|
|
|
describe("A markup generator", function() {
|
|
it("marks trees up", function() {
|
|
// Just a few quick sanity checks here...
|
|
var markup = katex.renderToString("\\sigma^2");
|
|
expect(markup.indexOf("<span")).toBe(0);
|
|
expect(markup).toContain("\u03c3"); // sigma
|
|
expect(markup).toContain("margin-right");
|
|
expect(markup).not.toContain("marginRight");
|
|
});
|
|
|
|
it("generates both MathML and HTML", function() {
|
|
var markup = katex.renderToString("a");
|
|
|
|
expect(markup).toContain("<span");
|
|
expect(markup).toContain("<math");
|
|
});
|
|
});
|
|
|
|
describe("An accent parser", function() {
|
|
it("should not fail", function() {
|
|
expect("\\vec{x}").toParse();
|
|
expect("\\vec{x^2}").toParse();
|
|
expect("\\vec{x}^2").toParse();
|
|
expect("\\vec x").toParse();
|
|
});
|
|
|
|
it("should produce accents", function() {
|
|
var parse = getParsed("\\vec x")[0];
|
|
|
|
expect(parse.type).toMatch("accent");
|
|
});
|
|
|
|
it("should be grouped more tightly than supsubs", function() {
|
|
var parse = getParsed("\\vec x^2")[0];
|
|
|
|
expect(parse.type).toMatch("supsub");
|
|
});
|
|
|
|
it("should not parse expanding accents", function() {
|
|
expect("\\widehat{x}").toNotParse();
|
|
});
|
|
});
|
|
|
|
describe("An accent builder", function() {
|
|
it("should not fail", function() {
|
|
expect("\\vec{x}").toBuild();
|
|
expect("\\vec{x}^2").toBuild();
|
|
expect("\\vec{x}_2").toBuild();
|
|
expect("\\vec{x}_2^2").toBuild();
|
|
});
|
|
|
|
it("should produce mords", function() {
|
|
expect(getBuilt("\\vec x")[0].classes).toContain("mord");
|
|
expect(getBuilt("\\vec +")[0].classes).toContain("mord");
|
|
expect(getBuilt("\\vec +")[0].classes).not.toContain("mbin");
|
|
expect(getBuilt("\\vec )^2")[0].classes).toContain("mord");
|
|
expect(getBuilt("\\vec )^2")[0].classes).not.toContain("mclose");
|
|
});
|
|
});
|
|
|
|
describe("A phantom parser", function() {
|
|
it("should not fail", function() {
|
|
expect("\\phantom{x}").toParse();
|
|
expect("\\phantom{x^2}").toParse();
|
|
expect("\\phantom{x}^2").toParse();
|
|
expect("\\phantom x").toParse();
|
|
});
|
|
|
|
it("should build a phantom node", function() {
|
|
var parse = getParsed("\\phantom{x}")[0];
|
|
|
|
expect(parse.type).toMatch("phantom");
|
|
expect(parse.value.value).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe("A phantom builder", function() {
|
|
it("should not fail", function() {
|
|
expect("\\phantom{x}").toBuild();
|
|
expect("\\phantom{x^2}").toBuild();
|
|
expect("\\phantom{x}^2").toBuild();
|
|
expect("\\phantom x").toBuild();
|
|
});
|
|
|
|
it("should make the children transparent", function() {
|
|
var children = getBuilt("\\phantom{x+1}")[0].children;
|
|
expect(children[0].style.color).toBe("transparent");
|
|
expect(children[1].style.color).toBe("transparent");
|
|
expect(children[2].style.color).toBe("transparent");
|
|
});
|
|
|
|
it("should make all descendants transparent", function() {
|
|
var children = getBuilt("\\phantom{x+\\blue{1}}")[0].children;
|
|
expect(children[0].style.color).toBe("transparent");
|
|
expect(children[1].style.color).toBe("transparent");
|
|
expect(children[2].children[0].style.color).toBe("transparent");
|
|
});
|
|
});
|
|
|
|
describe("A parser error", function () {
|
|
it("should report the position of an error", function () {
|
|
try {
|
|
parseTree("\\sqrt}", defaultSettings);
|
|
} catch (e) {
|
|
expect(e.position).toEqual(5);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("An optional argument parser", function() {
|
|
it("should not fail", function() {
|
|
// Note this doesn't actually make an optional argument, but still
|
|
// should work
|
|
expect("\\frac[1]{2}{3}").toParse();
|
|
|
|
expect("\\rule[0.2em]{1em}{1em}").toParse();
|
|
});
|
|
|
|
it("should fail on sqrts for now", function() {
|
|
expect("\\sqrt[3]{2}").toNotParse();
|
|
});
|
|
|
|
it("should work when the optional argument is missing", function() {
|
|
expect("\\sqrt{2}").toParse();
|
|
expect("\\rule{1em}{2em}").toParse();
|
|
});
|
|
|
|
it("should fail when the optional argument is malformed", function() {
|
|
expect("\\rule[1]{2em}{3em}").toNotParse();
|
|
});
|
|
|
|
it("should not work if the optional argument isn't closed", function() {
|
|
expect("\\sqrt[").toNotParse();
|
|
});
|
|
});
|
|
|
|
var getMathML = function(expr) {
|
|
expect(expr).toParse();
|
|
|
|
var built = buildMathML(parseTree(expr));
|
|
|
|
// Strip off the surrounding <span>
|
|
return built.children[0];
|
|
};
|
|
|
|
describe("A MathML builder", function() {
|
|
it("should generate math nodes", function() {
|
|
var node = getMathML("x^2");
|
|
|
|
expect(node.type).toEqual("math");
|
|
});
|
|
|
|
it("should generate appropriate MathML types", function() {
|
|
var identifier = getMathML("x").children[0].children[0];
|
|
expect(identifier.children[0].type).toEqual("mi");
|
|
|
|
var number = getMathML("1").children[0].children[0];
|
|
expect(number.children[0].type).toEqual("mn");
|
|
|
|
var operator = getMathML("+").children[0].children[0];
|
|
expect(operator.children[0].type).toEqual("mo");
|
|
|
|
var space = getMathML("\\;").children[0].children[0];
|
|
expect(space.children[0].type).toEqual("mspace");
|
|
|
|
var text = getMathML("\\text{a}").children[0].children[0];
|
|
expect(text.children[0].type).toEqual("mtext");
|
|
|
|
var textop = getMathML("\\sin").children[0].children[0];
|
|
expect(textop.children[0].type).toEqual("mi");
|
|
});
|
|
|
|
it("should generate a <mphantom> node for \\phantom", function() {
|
|
var phantom = getMathML("\\phantom{x}").children[0].children[0];
|
|
expect(phantom.children[0].type).toEqual("mphantom");
|
|
});
|
|
});
|