
Summary We'd like contributors to use the same linter and lint rules that we use internally. This diff swaps out eslint for jshint and fixes all lint failures except for the max-len failures in the test suites. Test Plan: - ka-lint src - make lint - make test Reviewers: emily
1779 lines
59 KiB
JavaScript
1779 lines
59 KiB
JavaScript
/* eslint max-len:0 */
|
|
/* global beforeEach: false */
|
|
/* global jasmine: false */
|
|
/* global expect: false */
|
|
/* global it: false */
|
|
/* global describe: false */
|
|
|
|
var buildMathML = require("../src/buildMathML");
|
|
var buildTree = require("../src/buildTree");
|
|
var katex = require("../katex");
|
|
var ParseError = require("../src/ParseError");
|
|
var parseTree = require("../src/parseTree");
|
|
var Options = require("../src/Options");
|
|
var Settings = require("../src/Settings");
|
|
var Style = require("../src/Style");
|
|
|
|
var defaultSettings = new Settings({});
|
|
var defaultOptions = new Options({
|
|
style: Style.TEXT,
|
|
size: "size5",
|
|
});
|
|
|
|
var _getBuilt = function(expr, settings) {
|
|
var usedSettings = settings ? settings : defaultSettings;
|
|
var parsedTree = parseTree(expr, usedSettings);
|
|
var rootNode = buildTree(parsedTree, expr, usedSettings);
|
|
|
|
// grab the root node of the HTML rendering
|
|
var builtHTML = rootNode.children[1];
|
|
|
|
// Remove the outer .katex and .katex-inner layers
|
|
return builtHTML.children[2].children;
|
|
};
|
|
|
|
/**
|
|
* Return the root node of the rendered HTML.
|
|
* @param expr
|
|
* @param settings
|
|
* @returns {Object}
|
|
*/
|
|
var getBuilt = function(expr, settings) {
|
|
var usedSettings = settings ? settings : defaultSettings;
|
|
expect(expr).toBuild(usedSettings);
|
|
return _getBuilt(expr, settings);
|
|
};
|
|
|
|
/**
|
|
* Return the root node of the parse tree.
|
|
* @param expr
|
|
* @param settings
|
|
* @returns {Object}
|
|
*/
|
|
var getParsed = function(expr, settings) {
|
|
var usedSettings = settings ? settings : defaultSettings;
|
|
|
|
expect(expr).toParse(usedSettings);
|
|
return parseTree(expr, usedSettings);
|
|
};
|
|
|
|
beforeEach(function() {
|
|
jasmine.addMatchers({
|
|
toParse: function() {
|
|
return {
|
|
compare: function(actual, settings) {
|
|
var usedSettings = settings ? settings : defaultSettings;
|
|
|
|
var result = {
|
|
pass: true,
|
|
message: "'" + actual + "' succeeded parsing",
|
|
};
|
|
|
|
try {
|
|
parseTree(actual, usedSettings);
|
|
} 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, settings) {
|
|
var usedSettings = settings ? settings : defaultSettings;
|
|
|
|
var result = {
|
|
pass: false,
|
|
message: "Expected '" + actual + "' to fail " +
|
|
"parsing, but it succeeded",
|
|
};
|
|
|
|
try {
|
|
parseTree(actual, usedSettings);
|
|
} 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, settings) {
|
|
var usedSettings = settings ? settings : defaultSettings;
|
|
|
|
var result = {
|
|
pass: true,
|
|
message: "'" + actual + "' succeeded in building",
|
|
};
|
|
|
|
expect(actual).toParse(usedSettings);
|
|
|
|
try {
|
|
_getBuilt(actual, settings);
|
|
} 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).toEqual("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).toEqual("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).toEqual("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).toEqual("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).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("_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 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() {
|
|
var 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() {
|
|
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).toEqual("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).toEqual("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).toEqual("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).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() {
|
|
var dfracParse = getParsed(dfracExpression)[0];
|
|
|
|
expect(dfracParse.type).toEqual("genfrac");
|
|
expect(dfracParse.value.numer).toBeDefined();
|
|
expect(dfracParse.value.denom).toBeDefined();
|
|
|
|
var tfracParse = getParsed(tfracExpression)[0];
|
|
|
|
expect(tfracParse.type).toEqual("genfrac");
|
|
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).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() {
|
|
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).toEqual("genfrac");
|
|
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).toEqual("genfrac");
|
|
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).toEqual("genfrac");
|
|
expect(parse.value.numer.value[0].type).toEqual("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).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() {
|
|
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).toEqual("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}}";
|
|
var mathTokenAfterText = "\\text{sin}^2";
|
|
|
|
it("should not fail", function() {
|
|
expect(textExpression).toParse();
|
|
});
|
|
|
|
it("should produce a text", function() {
|
|
var parse = getParsed(textExpression)[0];
|
|
|
|
expect(parse.type).toEqual("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).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() {
|
|
var parse = getParsed(spaceTextExpression)[0];
|
|
var 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() {
|
|
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 newColorExpression = "\\redA{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).toEqual("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).toEqual("#fA6");
|
|
});
|
|
|
|
it("should not parse a bad custom color", function() {
|
|
expect(badCustomColorExpression).toNotParse();
|
|
});
|
|
|
|
it("should parse new colors from the branding guide", function() {
|
|
expect(newColorExpression).toParse();
|
|
});
|
|
|
|
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).toEqual("spacing");
|
|
});
|
|
|
|
it("should produce spacing in text mode", function() {
|
|
var text = getParsed(textTie)[0];
|
|
var parse = text.value.body;
|
|
|
|
expect(parse[1].type).toEqual("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).toEqual("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).toEqual("delimsizing");
|
|
});
|
|
|
|
it("should produce the correct direction delimiter", function() {
|
|
var leftParse = getParsed(normalDelim)[0];
|
|
var rightParse = getParsed(bigDelim)[0];
|
|
|
|
expect(leftParse.value.delimType).toEqual("open");
|
|
expect(rightParse.value.delimType).toEqual("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).toEqual("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).toEqual("rule");
|
|
});
|
|
|
|
it("should list the correct units", function() {
|
|
var emParse = getParsed(emRule)[0];
|
|
var 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() {
|
|
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).toEqual("leftright");
|
|
expect(parse.value.left).toEqual("(");
|
|
expect(parse.value.right).toEqual(")");
|
|
});
|
|
|
|
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 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 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() {
|
|
var m1 = "\\begin{pmatrix}1&2\\\\3&4\\end{pmatrix}";
|
|
var 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();
|
|
});
|
|
});
|
|
|
|
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).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() {
|
|
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).toEqual("display");
|
|
|
|
var scriptscriptParse = getParsed("\\scriptscriptstyle x")[0];
|
|
expect(scriptscriptParse.value.style).toEqual("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).toEqual("styling");
|
|
|
|
var 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() {
|
|
var mathbbParse = getParsed("\\mathbb x")[0];
|
|
expect(mathbbParse.value.font).toEqual("mathbb");
|
|
expect(mathbbParse.value.type).toEqual("font");
|
|
|
|
var mathrmParse = getParsed("\\mathrm x")[0];
|
|
expect(mathrmParse.value.font).toEqual("mathrm");
|
|
expect(mathrmParse.value.type).toEqual("font");
|
|
|
|
var mathitParse = getParsed("\\mathit x")[0];
|
|
expect(mathitParse.value.font).toEqual("mathit");
|
|
expect(mathitParse.value.type).toEqual("font");
|
|
|
|
var mathcalParse = getParsed("\\mathcal C")[0];
|
|
expect(mathcalParse.value.font).toEqual("mathcal");
|
|
expect(mathcalParse.value.type).toEqual("font");
|
|
|
|
var mathfrakParse = getParsed("\\mathfrak C")[0];
|
|
expect(mathfrakParse.value.font).toEqual("mathfrak");
|
|
expect(mathfrakParse.value.type).toEqual("font");
|
|
});
|
|
|
|
it("should parse nested font commands", function() {
|
|
var 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);
|
|
var 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 \\color", function() {
|
|
var colorMathbbParse = getParsed("\\color{blue}{\\mathbb R}")[0];
|
|
expect(colorMathbbParse.value.type).toEqual("color");
|
|
expect(colorMathbbParse.value.color).toEqual("blue");
|
|
var 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() {
|
|
var 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("An HTML font tree-builder", function() {
|
|
it("should render \\mathbb{R} with the correct font", function() {
|
|
var markup = katex.renderToString("\\mathbb{R}");
|
|
expect(markup).toContain("<span class=\"mord mathbb\">R</span>");
|
|
});
|
|
|
|
it("should render \\mathrm{R} with the correct font", function() {
|
|
var markup = katex.renderToString("\\mathrm{R}");
|
|
expect(markup).toContain("<span class=\"mord mathrm\">R</span>");
|
|
});
|
|
|
|
it("should render \\mathcal{R} with the correct font", function() {
|
|
var markup = katex.renderToString("\\mathcal{R}");
|
|
expect(markup).toContain("<span class=\"mord mathcal\">R</span>");
|
|
});
|
|
|
|
it("should render \\mathfrak{R} with the correct font", function() {
|
|
var markup = katex.renderToString("\\mathfrak{R}");
|
|
expect(markup).toContain("<span class=\"mord mathfrak\">R</span>");
|
|
});
|
|
|
|
it("should render a combination of font and color changes", function() {
|
|
var markup = katex.renderToString("\\color{blue}{\\mathbb R}");
|
|
var span = "<span class=\"mord mathbb\" style=\"color:blue;\">R</span>";
|
|
expect(markup).toContain(span);
|
|
|
|
markup = katex.renderToString("\\mathbb{\\color{blue}{R}}");
|
|
span = "<span class=\"mord mathbb\" style=\"color:blue;\">R</span>";
|
|
expect(markup).toContain(span);
|
|
});
|
|
});
|
|
|
|
|
|
describe("A MathML font tree-builder", function() {
|
|
var contents = "Ax2k\\omega\\Omega\\imath+";
|
|
|
|
it("should render " + contents + " with the correct mathvariants", function() {
|
|
var tree = getParsed(contents);
|
|
var markup = buildMathML(tree, contents, defaultOptions).toMarkup();
|
|
expect(markup).toContain("<mi>A</mi>");
|
|
expect(markup).toContain("<mi>x</mi>");
|
|
expect(markup).toContain("<mn>2</mn>");
|
|
expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
|
|
expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
|
|
expect(markup).toContain("<mi>\u0131</mi>"); // \imath
|
|
expect(markup).toContain("<mo>+</mo>");
|
|
});
|
|
|
|
it("should render \\mathbb{" + contents + "} with the correct mathvariants", function() {
|
|
var tex = "\\mathbb{" + contents + "}";
|
|
var tree = getParsed(tex);
|
|
var markup = buildMathML(tree, tex, defaultOptions).toMarkup();
|
|
expect(markup).toContain("<mi mathvariant=\"double-struck\">A</mi>");
|
|
expect(markup).toContain("<mi>x</mi>");
|
|
expect(markup).toContain("<mn mathvariant=\"normal\">2</mn>");
|
|
expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
|
|
expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
|
|
expect(markup).toContain("<mi>\u0131</mi>"); // \imath
|
|
expect(markup).toContain("<mo>+</mo>");
|
|
});
|
|
|
|
it("should render \\mathrm{" + contents + "} with the correct mathvariants", function() {
|
|
var tex = "\\mathrm{" + contents + "}";
|
|
var tree = getParsed(tex);
|
|
var markup = buildMathML(tree, tex, defaultOptions).toMarkup();
|
|
expect(markup).toContain("<mi mathvariant=\"normal\">A</mi>");
|
|
expect(markup).toContain("<mi mathvariant=\"normal\">x</mi>");
|
|
expect(markup).toContain("<mn mathvariant=\"normal\">2</mn>");
|
|
expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
|
|
expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
|
|
expect(markup).toContain("<mi>\u0131</mi>"); // \imath
|
|
expect(markup).toContain("<mo>+</mo>");
|
|
});
|
|
|
|
it("should render \\mathit{" + contents + "} with the correct mathvariants", function() {
|
|
var tex = "\\mathit{" + contents + "}";
|
|
var tree = getParsed(tex);
|
|
var markup = buildMathML(tree, tex, defaultOptions).toMarkup();
|
|
expect(markup).toContain("<mi mathvariant=\"italic\">A</mi>");
|
|
expect(markup).toContain("<mi mathvariant=\"italic\">x</mi>");
|
|
expect(markup).toContain("<mn mathvariant=\"italic\">2</mn>");
|
|
expect(markup).toContain("<mi mathvariant=\"italic\">\u03c9</mi>"); // \omega
|
|
expect(markup).toContain("<mi mathvariant=\"italic\">\u03A9</mi>"); // \Omega
|
|
expect(markup).toContain("<mi mathvariant=\"italic\">\u0131</mi>"); // \imath
|
|
expect(markup).toContain("<mo>+</mo>");
|
|
});
|
|
|
|
it("should render \\mathbf{" + contents + "} with the correct mathvariants", function() {
|
|
var tex = "\\mathbf{" + contents + "}";
|
|
var tree = getParsed(tex);
|
|
var markup = buildMathML(tree, tex, defaultOptions).toMarkup();
|
|
expect(markup).toContain("<mi mathvariant=\"bold\">A</mi>");
|
|
expect(markup).toContain("<mi mathvariant=\"bold\">x</mi>");
|
|
expect(markup).toContain("<mn mathvariant=\"bold\">2</mn>");
|
|
expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
|
|
expect(markup).toContain("<mi mathvariant=\"bold\">\u03A9</mi>"); // \Omega
|
|
expect(markup).toContain("<mi>\u0131</mi>"); // \imath
|
|
expect(markup).toContain("<mo>+</mo>");
|
|
});
|
|
|
|
it("should render \\mathcal{" + contents + "} with the correct mathvariants", function() {
|
|
var tex = "\\mathcal{" + contents + "}";
|
|
var tree = getParsed(tex);
|
|
var markup = buildMathML(tree, tex, defaultOptions).toMarkup();
|
|
expect(markup).toContain("<mi mathvariant=\"script\">A</mi>");
|
|
expect(markup).toContain("<mi>x</mi>"); // script is caps only
|
|
expect(markup).toContain("<mn mathvariant=\"script\">2</mn>");
|
|
// MathJax marks everything below as "script" except \omega
|
|
// We don't have these glyphs in "caligraphic" and neither does MathJax
|
|
expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
|
|
expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
|
|
expect(markup).toContain("<mi>\u0131</mi>"); // \imath
|
|
expect(markup).toContain("<mo>+</mo>");
|
|
});
|
|
|
|
it("should render \\mathfrak{" + contents + "} with the correct mathvariants", function() {
|
|
var tex = "\\mathfrak{" + contents + "}";
|
|
var tree = getParsed(tex);
|
|
var markup = buildMathML(tree, tex, defaultOptions).toMarkup();
|
|
expect(markup).toContain("<mi mathvariant=\"fraktur\">A</mi>");
|
|
expect(markup).toContain("<mi mathvariant=\"fraktur\">x</mi>");
|
|
expect(markup).toContain("<mn mathvariant=\"fraktur\">2</mn>");
|
|
// MathJax marks everything below as "fraktur" except \omega
|
|
// We don't have these glyphs in "fraktur" and neither does MathJax
|
|
expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
|
|
expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
|
|
expect(markup).toContain("<mi>\u0131</mi>"); // \imath
|
|
expect(markup).toContain("<mo>+</mo>");
|
|
});
|
|
|
|
it("should render \\mathscr{" + contents + "} with the correct mathvariants", function() {
|
|
var tex = "\\mathscr{" + contents + "}";
|
|
var tree = getParsed(tex);
|
|
var markup = buildMathML(tree, tex, defaultOptions).toMarkup();
|
|
expect(markup).toContain("<mi mathvariant=\"script\">A</mi>");
|
|
// MathJax marks everything below as "script" except \omega
|
|
// We don't have these glyphs in "script" and neither does MathJax
|
|
expect(markup).toContain("<mi>x</mi>");
|
|
expect(markup).toContain("<mn mathvariant=\"normal\">2</mn>");
|
|
expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
|
|
expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
|
|
expect(markup).toContain("<mi>\u0131</mi>"); // \imath
|
|
expect(markup).toContain("<mo>+</mo>");
|
|
});
|
|
|
|
it("should render \\mathsf{" + contents + "} with the correct mathvariants", function() {
|
|
var tex = "\\mathsf{" + contents + "}";
|
|
var tree = getParsed(tex);
|
|
var markup = buildMathML(tree, tex, defaultOptions).toMarkup();
|
|
expect(markup).toContain("<mi mathvariant=\"sans-serif\">A</mi>");
|
|
expect(markup).toContain("<mi mathvariant=\"sans-serif\">x</mi>");
|
|
expect(markup).toContain("<mn mathvariant=\"sans-serif\">2</mn>");
|
|
expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
|
|
expect(markup).toContain("<mi mathvariant=\"sans-serif\">\u03A9</mi>"); // \Omega
|
|
expect(markup).toContain("<mi>\u0131</mi>"); // \imath
|
|
expect(markup).toContain("<mo>+</mo>");
|
|
});
|
|
|
|
it("should render a combination of font and color changes", function() {
|
|
var tex = "\\color{blue}{\\mathbb R}";
|
|
var tree = getParsed(tex);
|
|
var markup = buildMathML(tree, tex, defaultOptions).toMarkup();
|
|
var node = "<mstyle mathcolor=\"blue\">" +
|
|
"<mi mathvariant=\"double-struck\">R</mi>" +
|
|
"</mstyle>";
|
|
expect(markup).toContain(node);
|
|
|
|
// reverse the order of the commands
|
|
tex = "\\mathbb{\\color{blue}{R}}";
|
|
tree = getParsed(tex);
|
|
markup = buildMathML(tree, tex, defaultOptions).toMarkup();
|
|
node = "<mstyle mathcolor=\"blue\">" +
|
|
"<mi mathvariant=\"double-struck\">R</mi>" +
|
|
"</mstyle>";
|
|
expect(markup).toContain(node);
|
|
});
|
|
});
|
|
|
|
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("A parse tree generator", function() {
|
|
it("generates a tree", function() {
|
|
var tree = katex.__parse("\\sigma^2");
|
|
expect(JSON.stringify(tree)).toEqual(JSON.stringify([
|
|
{
|
|
"type": "supsub",
|
|
"value": {
|
|
"base": {
|
|
"type": "mathord",
|
|
"value": "\\sigma",
|
|
"mode": "math",
|
|
},
|
|
"sup": {
|
|
"type": "textord",
|
|
"value": "2",
|
|
"mode": "math",
|
|
},
|
|
"sub": undefined,
|
|
},
|
|
"mode": "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).toEqual("accent");
|
|
});
|
|
|
|
it("should be grouped more tightly than supsubs", function() {
|
|
var parse = getParsed("\\vec x^2")[0];
|
|
|
|
expect(parse.type).toEqual("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).toEqual("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 work with sqrts with optional arguments", function() {
|
|
expect("\\sqrt[3]{2}").toParse();
|
|
});
|
|
|
|
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();
|
|
});
|
|
});
|
|
|
|
describe("An array environment", function() {
|
|
|
|
it("should accept a single alignment character", function() {
|
|
var parse = getParsed("\\begin{array}r1\\\\20\\end{array}");
|
|
expect(parse[0].type).toBe("array");
|
|
expect(parse[0].value.cols).toEqual([
|
|
{ type: "align", align: "r" },
|
|
]);
|
|
});
|
|
|
|
it("should accept vertical separators", function() {
|
|
var parse = getParsed("\\begin{array}{|l||c|}\\end{array}");
|
|
expect(parse[0].type).toBe("array");
|
|
expect(parse[0].value.cols).toEqual([
|
|
{ type: "separator", separator: "|" },
|
|
{ type: "align", align: "l" },
|
|
{ type: "separator", separator: "|" },
|
|
{ type: "separator", separator: "|" },
|
|
{ type: "align", align: "c" },
|
|
{ type: "separator", separator: "|" },
|
|
]);
|
|
});
|
|
|
|
});
|
|
|
|
describe("A cases environment", function() {
|
|
|
|
it("should parse its input", function() {
|
|
expect("f(a,b)=\\begin{cases}a+1&\\text{if }b\\text{ is odd}\\\\" +
|
|
"a&\\text{if }b=0\\\\a-1&\\text{otherwise}\\end{cases}")
|
|
.toParse();
|
|
});
|
|
|
|
});
|
|
|
|
describe("An aligned environment", function() {
|
|
|
|
it("should parse its input", function() {
|
|
expect("\\begin{aligned}a&=b&c&=d\\\\e&=f\\end{aligned}")
|
|
.toParse();
|
|
});
|
|
|
|
});
|
|
|
|
var getMathML = function(expr, settings) {
|
|
var usedSettings = settings ? settings : defaultSettings;
|
|
|
|
expect(expr).toParse(usedSettings);
|
|
|
|
var built = buildMathML(parseTree(expr, usedSettings), expr, usedSettings);
|
|
|
|
// 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");
|
|
});
|
|
});
|
|
|
|
describe("A parser that does not throw on unsupported commands", function() {
|
|
// The parser breaks on unsupported commands unless it is explicitly
|
|
// told not to
|
|
var errorColor = "#933";
|
|
var 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() {
|
|
var parsedInput = getParsed("\\error", noThrowSettings);
|
|
expect(parsedInput[0].type).toBe("color");
|
|
expect(parsedInput[0].value.color).toBe(errorColor);
|
|
});
|
|
});
|
|
|
|
describe("The symbol table integraty", 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"));
|
|
});
|
|
});
|