mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-04 18:58:39 +00:00
* Support for top-level \newline and \\ in inline math This was a little tricky because `\\` was defined as an endOfExpression. Instead made `\\` a termination specific to an array environment. Outside an array environment, buildHTML handles the `cr` object, resulting in a `.newline` class. Currently this turns into a `display: block` (with appropriate vertical spacing) only in inline math, matching LaTeX. * Simplify code * Fix Jest errors * NewLine screenshot test * Bug fix: \\ only works at top level of inline * Add \newline and \cr to test * Switch test to pmatrix * Add vertical space test * Add \\ vs. \newline tests * Fix flow errors * Add \cr test * Add documentation for \\ at top level * Comment out newRow * Fix commenting out
3202 lines
111 KiB
JavaScript
3202 lines
111 KiB
JavaScript
/* eslint max-len:0 */
|
||
/* global beforeEach: false */
|
||
/* global expect: false */
|
||
/* global it: false */
|
||
/* global describe: false */
|
||
import stringify from 'json-stable-stringify';
|
||
|
||
import buildMathML from "../src/buildMathML";
|
||
import buildTree from "../src/buildTree";
|
||
import katex from "../katex";
|
||
import ParseError from "../src/ParseError";
|
||
import parseTree from "../src/parseTree";
|
||
import Options from "../src/Options";
|
||
import Settings from "../src/Settings";
|
||
import Style from "../src/Style";
|
||
|
||
const typeFirstCompare = (a, b) => {
|
||
if (a.key === 'type') {
|
||
return -1;
|
||
} else if (b.key === 'type') {
|
||
return 1;
|
||
} else {
|
||
return a.key < b.key ? -1 : 1;
|
||
}
|
||
};
|
||
|
||
const serializer = {
|
||
print(val) {
|
||
return stringify(val, {cmp: typeFirstCompare, space: ' '});
|
||
},
|
||
test() {
|
||
return true;
|
||
},
|
||
};
|
||
|
||
expect.addSnapshotSerializer(serializer);
|
||
|
||
const defaultSettings = new Settings({});
|
||
const defaultOptions = new Options({
|
||
style: Style.TEXT,
|
||
size: 5,
|
||
maxSize: Infinity,
|
||
});
|
||
|
||
const _getBuilt = function(expr, settings) {
|
||
const usedSettings = settings ? settings : defaultSettings;
|
||
const rootNode = katex.__renderToDomTree(expr, usedSettings);
|
||
|
||
if (rootNode.classes.indexOf('katex-error') >= 0) {
|
||
return rootNode;
|
||
}
|
||
|
||
// grab the root node of the HTML rendering
|
||
const builtHTML = rootNode.children[1];
|
||
|
||
// combine the non-strut children of all base spans
|
||
const children = [];
|
||
for (let i = 0; i < builtHTML.children.length; i++) {
|
||
children.push(...builtHTML.children[i].children.filter(
|
||
(node) => node.classes.indexOf("strut") < 0));
|
||
}
|
||
return children;
|
||
};
|
||
|
||
/**
|
||
* Return the root node of the rendered HTML.
|
||
* @param expr
|
||
* @param settings
|
||
* @returns {Object}
|
||
*/
|
||
const getBuilt = function(expr, settings) {
|
||
const 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}
|
||
*/
|
||
const getParsed = function(expr, settings) {
|
||
const usedSettings = settings ? settings : defaultSettings;
|
||
|
||
expect(expr).toParse(usedSettings);
|
||
return parseTree(expr, usedSettings);
|
||
};
|
||
|
||
const stripPositions = function(expr) {
|
||
if (typeof expr !== "object" || expr === null) {
|
||
return expr;
|
||
}
|
||
if (expr.loc && expr.loc.lexer && typeof expr.loc.start === "number") {
|
||
delete expr.loc;
|
||
}
|
||
Object.keys(expr).forEach(function(key) {
|
||
stripPositions(expr[key]);
|
||
});
|
||
return expr;
|
||
};
|
||
|
||
const parseAndSetResult = function(expr, result, settings) {
|
||
try {
|
||
return parseTree(expr, settings || defaultSettings);
|
||
} catch (e) {
|
||
result.pass = false;
|
||
if (e instanceof ParseError) {
|
||
result.message = () => "'" + expr + "' failed " +
|
||
"parsing with error: " + e.message;
|
||
} else {
|
||
result.message = () => "'" + expr + "' failed " +
|
||
"parsing with unknown error: " + e.message;
|
||
}
|
||
}
|
||
};
|
||
|
||
const buildAndSetResult = function(expr, result, settings) {
|
||
try {
|
||
return _getBuilt(expr, settings || defaultSettings);
|
||
} catch (e) {
|
||
result.pass = false;
|
||
if (e instanceof ParseError) {
|
||
result.message = () => "'" + expr + "' failed " +
|
||
"parsing with error: " + e.message;
|
||
} else {
|
||
result.message = () => "'" + expr + "' failed " +
|
||
"parsing with unknown error: " + e.message;
|
||
}
|
||
}
|
||
};
|
||
|
||
beforeEach(function() {
|
||
expect.extend({
|
||
toParse: function(actual, settings) {
|
||
const usedSettings = settings ? settings : defaultSettings;
|
||
|
||
const result = {
|
||
pass: true,
|
||
message: () => "'" + actual + "' succeeded parsing",
|
||
};
|
||
parseAndSetResult(actual, result, usedSettings);
|
||
return result;
|
||
},
|
||
|
||
toNotParse: function(actual, settings) {
|
||
const usedSettings = settings ? settings : defaultSettings;
|
||
|
||
const 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(actual, settings) {
|
||
const usedSettings = settings ? settings : defaultSettings;
|
||
|
||
const 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;
|
||
},
|
||
|
||
toParseLike: function(actual, expected, settings) {
|
||
const usedSettings = settings ? settings : defaultSettings;
|
||
|
||
const result = {
|
||
pass: true,
|
||
message: () => "Parse trees of '" + actual +
|
||
"' and '" + expected + "' are equivalent",
|
||
};
|
||
|
||
const actualTree = parseAndSetResult(actual, result,
|
||
usedSettings);
|
||
if (!actualTree) {
|
||
return result;
|
||
}
|
||
const expectedTree = parseAndSetResult(expected, result,
|
||
usedSettings);
|
||
if (!expectedTree) {
|
||
return result;
|
||
}
|
||
|
||
stripPositions(actualTree);
|
||
stripPositions(expectedTree);
|
||
|
||
if (JSON.stringify(actualTree) !== JSON.stringify(expectedTree)) {
|
||
result.pass = false;
|
||
result.message = () => "Parse trees of '" + actual +
|
||
"' and '" + expected + "' are not equivalent";
|
||
}
|
||
return result;
|
||
},
|
||
|
||
toBuildLike: function(actual, expected, settings) {
|
||
const usedSettings = settings ? settings : defaultSettings;
|
||
|
||
const result = {
|
||
pass: true,
|
||
message: () => "Build trees of '" + actual +
|
||
"' and '" + expected + "' are equivalent",
|
||
};
|
||
|
||
const actualTree = buildAndSetResult(actual, result,
|
||
usedSettings);
|
||
if (!actualTree) {
|
||
return result;
|
||
}
|
||
const expectedTree = buildAndSetResult(expected, result,
|
||
usedSettings);
|
||
if (!expectedTree) {
|
||
return result;
|
||
}
|
||
|
||
stripPositions(actualTree);
|
||
stripPositions(expectedTree);
|
||
|
||
if (JSON.stringify(actualTree) !== JSON.stringify(expectedTree)) {
|
||
result.pass = false;
|
||
result.message = () => "Parse trees of '" + actual +
|
||
"' and '" + expected + "' are not equivalent";
|
||
}
|
||
return result;
|
||
},
|
||
});
|
||
});
|
||
|
||
describe("A parser", function() {
|
||
it("should not fail on an empty string", function() {
|
||
expect("").toParse();
|
||
});
|
||
|
||
it("should ignore whitespace", function() {
|
||
expect(" x y ").toParseLike("xy");
|
||
});
|
||
|
||
it("should ignore whitespace in atom", function() {
|
||
expect(" x ^ y ").toParseLike("x^y");
|
||
});
|
||
});
|
||
|
||
describe("An ord parser", function() {
|
||
const expression = "1234|/@.\"`abcdefgzABCDEFGZ";
|
||
|
||
it("should not fail", function() {
|
||
expect(expression).toParse();
|
||
});
|
||
|
||
it("should build a list of ords", function() {
|
||
const parse = getParsed(expression);
|
||
|
||
expect(parse).toBeTruthy();
|
||
|
||
for (let i = 0; i < parse.length; i++) {
|
||
const group = parse[i];
|
||
expect(group.type).toMatch("ord");
|
||
}
|
||
});
|
||
|
||
it("should parse the right number of ords", function() {
|
||
const parse = getParsed(expression);
|
||
|
||
expect(parse.length).toBe(expression.length);
|
||
});
|
||
});
|
||
|
||
describe("A bin parser", function() {
|
||
const expression = "+-*\\cdot\\pm\\div";
|
||
|
||
it("should not fail", function() {
|
||
expect(expression).toParse();
|
||
});
|
||
|
||
it("should build a list of bins", function() {
|
||
const parse = getParsed(expression);
|
||
expect(parse).toBeTruthy();
|
||
|
||
for (let i = 0; i < parse.length; i++) {
|
||
const group = parse[i];
|
||
expect(group.type).toEqual("bin");
|
||
}
|
||
});
|
||
});
|
||
|
||
describe("A rel parser", function() {
|
||
const expression = "=<>\\leq\\geq\\neq\\nleq\\ngeq\\cong";
|
||
const notExpression = "\\not=\\not<\\not>\\not\\leq\\not\\geq\\not\\in";
|
||
|
||
it("should not fail", function() {
|
||
expect(expression).toParse();
|
||
expect(notExpression).toParse();
|
||
});
|
||
|
||
it("should build a list of rels", function() {
|
||
const parse = getParsed(expression);
|
||
expect(parse).toBeTruthy();
|
||
|
||
for (let i = 0; i < parse.length; i++) {
|
||
const group = parse[i];
|
||
expect(group.type).toEqual("rel");
|
||
}
|
||
});
|
||
});
|
||
|
||
describe("A punct parser", function() {
|
||
const expression = ",;\\colon";
|
||
|
||
it("should not fail", function() {
|
||
expect(expression).toParse();
|
||
});
|
||
|
||
it("should build a list of puncts", function() {
|
||
const parse = getParsed(expression);
|
||
expect(parse).toBeTruthy();
|
||
|
||
for (let i = 0; i < parse.length; i++) {
|
||
const group = parse[i];
|
||
expect(group.type).toEqual("punct");
|
||
}
|
||
});
|
||
});
|
||
|
||
describe("An open parser", function() {
|
||
const expression = "([";
|
||
|
||
it("should not fail", function() {
|
||
expect(expression).toParse();
|
||
});
|
||
|
||
it("should build a list of opens", function() {
|
||
const parse = getParsed(expression);
|
||
expect(parse).toBeTruthy();
|
||
|
||
for (let i = 0; i < parse.length; i++) {
|
||
const group = parse[i];
|
||
expect(group.type).toEqual("open");
|
||
}
|
||
});
|
||
});
|
||
|
||
describe("A close parser", function() {
|
||
const expression = ")]?!";
|
||
|
||
it("should not fail", function() {
|
||
expect(expression).toParse();
|
||
});
|
||
|
||
it("should build a list of closes", function() {
|
||
const parse = getParsed(expression);
|
||
expect(parse).toBeTruthy();
|
||
|
||
for (let i = 0; i < parse.length; i++) {
|
||
const group = parse[i];
|
||
expect(group.type).toEqual("close");
|
||
}
|
||
});
|
||
});
|
||
|
||
describe("A \\KaTeX parser", function() {
|
||
it("should not fail", function() {
|
||
expect("\\KaTeX").toParse();
|
||
});
|
||
});
|
||
|
||
describe("A subscript and superscript parser", function() {
|
||
it("should not fail on superscripts", function() {
|
||
expect("x^2").toParse();
|
||
});
|
||
|
||
it("should not fail on subscripts", function() {
|
||
expect("x_3").toParse();
|
||
});
|
||
|
||
it("should not fail on both subscripts and superscripts", function() {
|
||
expect("x^2_3").toParse();
|
||
|
||
expect("x_2^3").toParse();
|
||
});
|
||
|
||
it("should not fail when there is no nucleus", function() {
|
||
expect("^3").toParse();
|
||
expect("^3+").toParse();
|
||
expect("_2").toParse();
|
||
expect("^3_2").toParse();
|
||
expect("_2^3").toParse();
|
||
});
|
||
|
||
it("should produce supsubs for superscript", function() {
|
||
const parse = getParsed("x^2")[0];
|
||
|
||
expect(parse.type).toBe("supsub");
|
||
expect(parse.value.base).toBeDefined();
|
||
expect(parse.value.sup).toBeDefined();
|
||
expect(parse.value.sub).toBeUndefined();
|
||
});
|
||
|
||
it("should produce supsubs for subscript", function() {
|
||
const parse = getParsed("x_3")[0];
|
||
|
||
expect(parse.type).toBe("supsub");
|
||
expect(parse.value.base).toBeDefined();
|
||
expect(parse.value.sub).toBeDefined();
|
||
expect(parse.value.sup).toBeUndefined();
|
||
});
|
||
|
||
it("should produce supsubs for ^_", function() {
|
||
const parse = getParsed("x^2_3")[0];
|
||
|
||
expect(parse.type).toBe("supsub");
|
||
expect(parse.value.base).toBeDefined();
|
||
expect(parse.value.sup).toBeDefined();
|
||
expect(parse.value.sub).toBeDefined();
|
||
});
|
||
|
||
it("should produce supsubs for _^", function() {
|
||
const parse = getParsed("x_3^2")[0];
|
||
|
||
expect(parse.type).toBe("supsub");
|
||
expect(parse.value.base).toBeDefined();
|
||
expect(parse.value.sup).toBeDefined();
|
||
expect(parse.value.sub).toBeDefined();
|
||
});
|
||
|
||
it("should produce the same thing regardless of order", function() {
|
||
const parseA = stripPositions(getParsed("x^2_3"));
|
||
const parseB = stripPositions(getParsed("x_3^2"));
|
||
|
||
expect(parseA).toEqual(parseB);
|
||
});
|
||
|
||
it("should not parse double subscripts or superscripts", function() {
|
||
expect("x^x^x").toNotParse();
|
||
|
||
expect("x_x_x").toNotParse();
|
||
|
||
expect("x_x^x_x").toNotParse();
|
||
|
||
expect("x_x^x^x").toNotParse();
|
||
|
||
expect("x^x_x_x").toNotParse();
|
||
|
||
expect("x^x_x^x").toNotParse();
|
||
});
|
||
|
||
it("should work correctly with {}s", function() {
|
||
expect("x^{2+3}").toParse();
|
||
|
||
expect("x_{3-2}").toParse();
|
||
|
||
expect("x^{2+3}_3").toParse();
|
||
|
||
expect("x^2_{3-2}").toParse();
|
||
|
||
expect("x^{2+3}_{3-2}").toParse();
|
||
|
||
expect("x_{3-2}^{2+3}").toParse();
|
||
|
||
expect("x_3^{2+3}").toParse();
|
||
|
||
expect("x_{3-2}^2").toParse();
|
||
});
|
||
|
||
it("should work with nested super/subscripts", function() {
|
||
expect("x^{x^x}").toParse();
|
||
expect("x^{x_x}").toParse();
|
||
expect("x_{x^x}").toParse();
|
||
expect("x_{x_x}").toParse();
|
||
});
|
||
});
|
||
|
||
describe("A subscript and superscript tree-builder", function() {
|
||
it("should not fail when there is no nucleus", function() {
|
||
expect("^3").toBuild();
|
||
expect("_2").toBuild();
|
||
expect("^3_2").toBuild();
|
||
expect("_2^3").toBuild();
|
||
});
|
||
});
|
||
|
||
describe("A parser with limit controls", function() {
|
||
it("should fail when the limit control is not preceded by an op node", function() {
|
||
expect("3\\nolimits_2^2").toNotParse();
|
||
expect("\\sqrt\\limits_2^2").toNotParse();
|
||
expect("45 +\\nolimits 45").toNotParse();
|
||
});
|
||
|
||
it("should parse when the limit control directly follows an op node", function() {
|
||
expect("\\int\\limits_2^2 3").toParse();
|
||
expect("\\sum\\nolimits_3^4 4").toParse();
|
||
});
|
||
|
||
it("should parse when the limit control is in the sup/sub area of an op node", function() {
|
||
expect("\\int_2^2\\limits").toParse();
|
||
expect("\\int^2\\nolimits_2").toParse();
|
||
expect("\\int_2\\limits^2").toParse();
|
||
});
|
||
|
||
it("should allow multiple limit controls in the sup/sub area of an op node", function() {
|
||
expect("\\int_2\\nolimits^2\\limits 3").toParse();
|
||
expect("\\int\\nolimits\\limits_2^2").toParse();
|
||
expect("\\int\\limits\\limits\\limits_2^2").toParse();
|
||
});
|
||
|
||
it("should have the rightmost limit control determine the limits property " +
|
||
"of the preceding op node", function() {
|
||
|
||
let parsedInput = getParsed("\\int\\nolimits\\limits_2^2");
|
||
expect(parsedInput[0].value.base.value.limits).toBe(true);
|
||
|
||
parsedInput = getParsed("\\int\\limits_2\\nolimits^2");
|
||
expect(parsedInput[0].value.base.value.limits).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe("A group parser", function() {
|
||
it("should not fail", function() {
|
||
expect("{xy}").toParse();
|
||
});
|
||
|
||
it("should produce a single ord", function() {
|
||
const parse = getParsed("{xy}");
|
||
|
||
expect(parse.length).toBe(1);
|
||
|
||
const ord = parse[0];
|
||
|
||
expect(ord.type).toMatch("ord");
|
||
expect(ord.value).toBeTruthy();
|
||
});
|
||
});
|
||
|
||
describe("An implicit group parser", function() {
|
||
it("should not fail", function() {
|
||
expect("\\Large x").toParse();
|
||
expect("abc {abc \\Large xyz} abc").toParse();
|
||
});
|
||
|
||
it("should produce a single object", function() {
|
||
const parse = getParsed("\\Large abc");
|
||
|
||
expect(parse.length).toBe(1);
|
||
|
||
const sizing = parse[0];
|
||
|
||
expect(sizing.type).toEqual("sizing");
|
||
expect(sizing.value).toBeTruthy();
|
||
});
|
||
|
||
it("should apply only after the function", function() {
|
||
const parse = getParsed("a \\Large abc");
|
||
|
||
expect(parse.length).toBe(2);
|
||
|
||
const sizing = parse[1];
|
||
|
||
expect(sizing.type).toEqual("sizing");
|
||
expect(sizing.value.value.length).toBe(3);
|
||
});
|
||
|
||
it("should stop at the ends of groups", function() {
|
||
const parse = getParsed("a { b \\Large c } d");
|
||
|
||
const group = parse[1];
|
||
const sizing = group.value[1];
|
||
|
||
expect(sizing.type).toEqual("sizing");
|
||
expect(sizing.value.value.length).toBe(1);
|
||
});
|
||
|
||
describe("within optional groups", () => {
|
||
it("should work with sizing commands: \\sqrt[\\small 3]{x}", () => {
|
||
const tree = stripPositions(getParsed("\\sqrt[\\small 3]{x}"));
|
||
expect(tree).toMatchSnapshot();
|
||
});
|
||
|
||
it("should work with \\color: \\sqrt[\\color{red} 3]{x}", () => {
|
||
const tree = stripPositions(getParsed("\\sqrt[\\color{red} 3]{x}"));
|
||
expect(tree).toMatchSnapshot();
|
||
});
|
||
|
||
it("should work style commands \\sqrt[\\textstyle 3]{x}", () => {
|
||
const tree = stripPositions(getParsed("\\sqrt[\\textstyle 3]{x}"));
|
||
expect(tree).toMatchSnapshot();
|
||
});
|
||
|
||
it("should work with old font functions: \\sqrt[\\tt 3]{x}", () => {
|
||
const tree = stripPositions(getParsed("\\sqrt[\\tt 3]{x}"));
|
||
expect(tree).toMatchSnapshot();
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("A function parser", function() {
|
||
it("should parse no argument functions", function() {
|
||
expect("\\div").toParse();
|
||
});
|
||
|
||
it("should parse 1 argument functions", function() {
|
||
expect("\\blue x").toParse();
|
||
});
|
||
|
||
it("should parse 2 argument functions", function() {
|
||
expect("\\frac 1 2").toParse();
|
||
});
|
||
|
||
it("should not parse 1 argument functions with no arguments", function() {
|
||
expect("\\blue").toNotParse();
|
||
});
|
||
|
||
it("should not parse 2 argument functions with 0 or 1 arguments", function() {
|
||
expect("\\frac").toNotParse();
|
||
|
||
expect("\\frac 1").toNotParse();
|
||
});
|
||
|
||
it("should not parse a function with text right after it", function() {
|
||
expect("\\redx").toNotParse();
|
||
});
|
||
|
||
it("should parse a function with a number right after it", function() {
|
||
expect("\\frac12").toParse();
|
||
});
|
||
|
||
it("should parse some functions with text right after it", function() {
|
||
expect("\\;x").toParse();
|
||
});
|
||
});
|
||
|
||
describe("A frac parser", function() {
|
||
const expression = "\\frac{x}{y}";
|
||
const dfracExpression = "\\dfrac{x}{y}";
|
||
const tfracExpression = "\\tfrac{x}{y}";
|
||
|
||
it("should not fail", function() {
|
||
expect(expression).toParse();
|
||
});
|
||
|
||
it("should produce a frac", function() {
|
||
const parse = getParsed(expression)[0];
|
||
|
||
expect(parse.type).toEqual("genfrac");
|
||
expect(parse.value.numer).toBeDefined();
|
||
expect(parse.value.denom).toBeDefined();
|
||
});
|
||
|
||
it("should also parse dfrac and tfrac", function() {
|
||
expect(dfracExpression).toParse();
|
||
|
||
expect(tfracExpression).toParse();
|
||
});
|
||
|
||
it("should parse dfrac and tfrac as fracs", function() {
|
||
const dfracParse = getParsed(dfracExpression)[0];
|
||
|
||
expect(dfracParse.type).toEqual("genfrac");
|
||
expect(dfracParse.value.numer).toBeDefined();
|
||
expect(dfracParse.value.denom).toBeDefined();
|
||
|
||
const tfracParse = getParsed(tfracExpression)[0];
|
||
|
||
expect(tfracParse.type).toEqual("genfrac");
|
||
expect(tfracParse.value.numer).toBeDefined();
|
||
expect(tfracParse.value.denom).toBeDefined();
|
||
});
|
||
|
||
it("should parse atop", function() {
|
||
const parse = getParsed("x \\atop y")[0];
|
||
|
||
expect(parse.type).toEqual("genfrac");
|
||
expect(parse.value.numer).toBeDefined();
|
||
expect(parse.value.denom).toBeDefined();
|
||
expect(parse.value.hasBarLine).toEqual(false);
|
||
});
|
||
});
|
||
|
||
describe("An over parser", function() {
|
||
const simpleOver = "1 \\over x";
|
||
const complexOver = "1+2i \\over 3+4i";
|
||
|
||
it("should not fail", function() {
|
||
expect(simpleOver).toParse();
|
||
expect(complexOver).toParse();
|
||
});
|
||
|
||
it("should produce a frac", function() {
|
||
let parse;
|
||
|
||
parse = getParsed(simpleOver)[0];
|
||
|
||
expect(parse.type).toEqual("genfrac");
|
||
expect(parse.value.numer).toBeDefined();
|
||
expect(parse.value.denom).toBeDefined();
|
||
|
||
parse = getParsed(complexOver)[0];
|
||
|
||
expect(parse.type).toEqual("genfrac");
|
||
expect(parse.value.numer).toBeDefined();
|
||
expect(parse.value.denom).toBeDefined();
|
||
});
|
||
|
||
it("should create a numerator from the atoms before \\over", function() {
|
||
const parse = getParsed(complexOver)[0];
|
||
|
||
const numer = parse.value.numer;
|
||
expect(numer.value.length).toEqual(4);
|
||
});
|
||
|
||
it("should create a demonimator from the atoms after \\over", function() {
|
||
const parse = getParsed(complexOver)[0];
|
||
|
||
const denom = parse.value.numer;
|
||
expect(denom.value.length).toEqual(4);
|
||
});
|
||
|
||
it("should handle empty numerators", function() {
|
||
const emptyNumerator = "\\over x";
|
||
const parse = getParsed(emptyNumerator)[0];
|
||
expect(parse.type).toEqual("genfrac");
|
||
expect(parse.value.numer).toBeDefined();
|
||
expect(parse.value.denom).toBeDefined();
|
||
});
|
||
|
||
it("should handle empty denominators", function() {
|
||
const emptyDenominator = "1 \\over";
|
||
const parse = getParsed(emptyDenominator)[0];
|
||
expect(parse.type).toEqual("genfrac");
|
||
expect(parse.value.numer).toBeDefined();
|
||
expect(parse.value.denom).toBeDefined();
|
||
});
|
||
|
||
it("should handle \\displaystyle correctly", function() {
|
||
const displaystyleExpression = "\\displaystyle 1 \\over 2";
|
||
const parse = getParsed(displaystyleExpression)[0];
|
||
expect(parse.type).toEqual("genfrac");
|
||
expect(parse.value.numer.value[0].type).toEqual("styling");
|
||
expect(parse.value.denom).toBeDefined();
|
||
});
|
||
|
||
it("should handle \\textstyle correctly", function() {
|
||
expect("\\textstyle 1 \\over 2")
|
||
.toParseLike("\\frac{\\textstyle 1}{2}");
|
||
expect("{\\textstyle 1} \\over 2")
|
||
.toParseLike("\\frac{\\textstyle 1}{2}");
|
||
});
|
||
|
||
it("should handle nested factions", function() {
|
||
const nestedOverExpression = "{1 \\over 2} \\over 3";
|
||
const parse = getParsed(nestedOverExpression)[0];
|
||
expect(parse.type).toEqual("genfrac");
|
||
expect(parse.value.numer.value[0].type).toEqual("genfrac");
|
||
expect(parse.value.numer.value[0].value.numer.value[0].value).toEqual("1");
|
||
expect(parse.value.numer.value[0].value.denom.value[0].value).toEqual("2");
|
||
expect(parse.value.denom).toBeDefined();
|
||
expect(parse.value.denom.value[0].value).toEqual("3");
|
||
});
|
||
|
||
it("should fail with multiple overs in the same group", function() {
|
||
const badMultipleOvers = "1 \\over 2 + 3 \\over 4";
|
||
expect(badMultipleOvers).toNotParse();
|
||
|
||
const badOverChoose = "1 \\over 2 \\choose 3";
|
||
expect(badOverChoose).toNotParse();
|
||
});
|
||
});
|
||
|
||
describe("A sizing parser", function() {
|
||
const sizeExpression = "\\Huge{x}\\small{x}";
|
||
|
||
it("should not fail", function() {
|
||
expect(sizeExpression).toParse();
|
||
});
|
||
|
||
it("should produce a sizing node", function() {
|
||
const parse = getParsed(sizeExpression)[0];
|
||
|
||
expect(parse.type).toEqual("sizing");
|
||
expect(parse.value).toBeDefined();
|
||
});
|
||
});
|
||
|
||
describe("A text parser", function() {
|
||
const textExpression = "\\text{a b}";
|
||
const noBraceTextExpression = "\\text x";
|
||
const nestedTextExpression =
|
||
"\\text{a {b} \\blue{c} \\textcolor{#fff}{x} \\llap{x}}";
|
||
const spaceTextExpression = "\\text{ a \\ }";
|
||
const leadingSpaceTextExpression = "\\text {moo}";
|
||
const badTextExpression = "\\text{a b%}";
|
||
const badFunctionExpression = "\\text{\\sqrt{x}}";
|
||
const mathTokenAfterText = "\\text{sin}^2";
|
||
|
||
it("should not fail", function() {
|
||
expect(textExpression).toParse();
|
||
});
|
||
|
||
it("should produce a text", function() {
|
||
const parse = getParsed(textExpression)[0];
|
||
|
||
expect(parse.type).toEqual("text");
|
||
expect(parse.value).toBeDefined();
|
||
});
|
||
|
||
it("should produce textords instead of mathords", function() {
|
||
const parse = getParsed(textExpression)[0];
|
||
const group = parse.value.body;
|
||
|
||
expect(group[0].type).toEqual("textord");
|
||
});
|
||
|
||
it("should not parse bad text", function() {
|
||
expect(badTextExpression).toNotParse();
|
||
});
|
||
|
||
it("should not parse bad functions inside text", function() {
|
||
expect(badFunctionExpression).toNotParse();
|
||
});
|
||
|
||
it("should parse text with no braces around it", function() {
|
||
expect(noBraceTextExpression).toParse();
|
||
});
|
||
|
||
it("should parse nested expressions", function() {
|
||
expect(nestedTextExpression).toParse();
|
||
});
|
||
|
||
it("should contract spaces", function() {
|
||
const parse = getParsed(spaceTextExpression)[0];
|
||
const group = parse.value.body;
|
||
|
||
expect(group[0].type).toEqual("spacing");
|
||
expect(group[1].type).toEqual("textord");
|
||
expect(group[2].type).toEqual("spacing");
|
||
expect(group[3].type).toEqual("spacing");
|
||
});
|
||
|
||
it("should accept math mode tokens after its argument", function() {
|
||
expect(mathTokenAfterText).toParse();
|
||
});
|
||
|
||
it("should ignore a space before the text group", function() {
|
||
const parse = getParsed(leadingSpaceTextExpression)[0];
|
||
// [m, o, o]
|
||
expect(parse.value.body.length).toBe(3);
|
||
expect(
|
||
parse.value.body.map(function(n) { return n.value; }).join("")
|
||
).toBe("moo");
|
||
});
|
||
|
||
it("should parse math within text group", function() {
|
||
expect("\\text{graph: $y = mx + b$}").toParse();
|
||
expect("\\text{graph: \\(y = mx + b\\)}").toParse();
|
||
});
|
||
|
||
it("should parse math within text within math within text", function() {
|
||
expect("\\text{hello $x + \\text{world $y$} + z$}").toParse();
|
||
expect("\\text{hello \\(x + \\text{world $y$} + z\\)}").toParse();
|
||
expect("\\text{hello $x + \\text{world \\(y\\)} + z$}").toParse();
|
||
expect("\\text{hello \\(x + \\text{world \\(y\\)} + z\\)}").toParse();
|
||
});
|
||
|
||
it("should forbid \\( within math mode", function() {
|
||
expect("\\(").toNotParse();
|
||
expect("\\text{$\\(x\\)$}").toNotParse();
|
||
});
|
||
|
||
it("should forbid $ within math mode", function() {
|
||
expect("$x$").toNotParse();
|
||
expect("\\text{\\($x$\\)}").toNotParse();
|
||
});
|
||
|
||
it("should detect unbalanced \\)", function() {
|
||
expect("\\)").toNotParse();
|
||
expect("\\text{\\)}").toNotParse();
|
||
});
|
||
|
||
it("should detect unbalanced $", function() {
|
||
expect("$").toNotParse();
|
||
expect("\\text{$}").toNotParse();
|
||
});
|
||
|
||
it("should not mix $ and \\(..\\)", function() {
|
||
expect("\\text{$x\\)}").toNotParse();
|
||
expect("\\text{\\(x$}").toNotParse();
|
||
});
|
||
|
||
it("should parse spacing functions", function() {
|
||
expect("a b\\, \\; \\! \\: ~ \\thinspace \\medspace \\quad \\ ").toBuild();
|
||
expect("\\enspace \\thickspace \\qquad \\space \\nobreakspace").toBuild();
|
||
});
|
||
|
||
it("should omit spaces after commands", function() {
|
||
expect("\\text{\\textellipsis !}")
|
||
.toParseLike("\\text{\\textellipsis!}");
|
||
});
|
||
});
|
||
|
||
describe("A color parser", function() {
|
||
const colorExpression = "\\blue{x}";
|
||
const newColorExpression = "\\redA{x}";
|
||
const customColorExpression1 = "\\textcolor{#fA6}{x}";
|
||
const customColorExpression2 = "\\textcolor{#fA6fA6}{x}";
|
||
const badCustomColorExpression1 = "\\textcolor{bad-color}{x}";
|
||
const badCustomColorExpression2 = "\\textcolor{#fA6f}{x}";
|
||
const badCustomColorExpression3 = "\\textcolor{#gA6}{x}";
|
||
const oldColorExpression = "\\color{#fA6}xy";
|
||
|
||
it("should not fail", function() {
|
||
expect(colorExpression).toParse();
|
||
});
|
||
|
||
it("should build a color node", function() {
|
||
const parse = getParsed(colorExpression)[0];
|
||
|
||
expect(parse.type).toEqual("color");
|
||
expect(parse.value.color).toBeDefined();
|
||
expect(parse.value.value).toBeDefined();
|
||
});
|
||
|
||
it("should parse a custom color", function() {
|
||
expect(customColorExpression1).toParse();
|
||
expect(customColorExpression2).toParse();
|
||
});
|
||
|
||
it("should correctly extract the custom color", function() {
|
||
const parse1 = getParsed(customColorExpression1)[0];
|
||
const parse2 = getParsed(customColorExpression2)[0];
|
||
|
||
expect(parse1.value.color).toEqual("#fA6");
|
||
expect(parse2.value.color).toEqual("#fA6fA6");
|
||
});
|
||
|
||
it("should not parse a bad custom color", function() {
|
||
expect(badCustomColorExpression1).toNotParse();
|
||
expect(badCustomColorExpression2).toNotParse();
|
||
expect(badCustomColorExpression3).toNotParse();
|
||
});
|
||
|
||
it("should parse new colors from the branding guide", function() {
|
||
expect(newColorExpression).toParse();
|
||
});
|
||
|
||
it("should have correct greediness", function() {
|
||
expect("\\textcolor{red}a").toParse();
|
||
expect("\\textcolor{red}{\\text{a}}").toParse();
|
||
expect("\\textcolor{red}\\text{a}").toNotParse();
|
||
expect("\\textcolor{red}\\frac12").toNotParse();
|
||
});
|
||
|
||
it("should use one-argument \\color by default", function() {
|
||
expect(oldColorExpression).toParseLike("\\textcolor{#fA6}{xy}");
|
||
});
|
||
|
||
it("should use one-argument \\color if requested", function() {
|
||
expect(oldColorExpression).toParseLike("\\textcolor{#fA6}{xy}", {
|
||
colorIsTextColor: false,
|
||
});
|
||
});
|
||
|
||
it("should use two-argument \\color if requested", function() {
|
||
expect(oldColorExpression).toParseLike("\\textcolor{#fA6}{x}y", {
|
||
colorIsTextColor: true,
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("A tie parser", function() {
|
||
const mathTie = "a~b";
|
||
const textTie = "\\text{a~ b}";
|
||
|
||
it("should parse ties in math mode", function() {
|
||
expect(mathTie).toParse();
|
||
});
|
||
|
||
it("should parse ties in text mode", function() {
|
||
expect(textTie).toParse();
|
||
});
|
||
|
||
it("should produce spacing in math mode", function() {
|
||
const parse = getParsed(mathTie);
|
||
|
||
expect(parse[1].type).toEqual("spacing");
|
||
});
|
||
|
||
it("should produce spacing in text mode", function() {
|
||
const text = getParsed(textTie)[0];
|
||
const parse = text.value.body;
|
||
|
||
expect(parse[1].type).toEqual("spacing");
|
||
});
|
||
|
||
it("should not contract with spaces in text mode", function() {
|
||
const text = getParsed(textTie)[0];
|
||
const parse = text.value.body;
|
||
|
||
expect(parse[2].type).toEqual("spacing");
|
||
});
|
||
});
|
||
|
||
describe("A delimiter sizing parser", function() {
|
||
const normalDelim = "\\bigl |";
|
||
const notDelim = "\\bigl x";
|
||
const bigDelim = "\\Biggr \\langle";
|
||
|
||
it("should parse normal delimiters", function() {
|
||
expect(normalDelim).toParse();
|
||
expect(bigDelim).toParse();
|
||
});
|
||
|
||
it("should not parse not-delimiters", function() {
|
||
expect(notDelim).toNotParse();
|
||
});
|
||
|
||
it("should produce a delimsizing", function() {
|
||
const parse = getParsed(normalDelim)[0];
|
||
|
||
expect(parse.type).toEqual("delimsizing");
|
||
});
|
||
|
||
it("should produce the correct direction delimiter", function() {
|
||
const leftParse = getParsed(normalDelim)[0];
|
||
const rightParse = getParsed(bigDelim)[0];
|
||
|
||
expect(leftParse.value.mclass).toEqual("mopen");
|
||
expect(rightParse.value.mclass).toEqual("mclose");
|
||
});
|
||
|
||
it("should parse the correct size delimiter", function() {
|
||
const smallParse = getParsed(normalDelim)[0];
|
||
const bigParse = getParsed(bigDelim)[0];
|
||
|
||
expect(smallParse.value.size).toEqual(1);
|
||
expect(bigParse.value.size).toEqual(4);
|
||
});
|
||
});
|
||
|
||
describe("An overline parser", function() {
|
||
const overline = "\\overline{x}";
|
||
|
||
it("should not fail", function() {
|
||
expect(overline).toParse();
|
||
});
|
||
|
||
it("should produce an overline", function() {
|
||
const parse = getParsed(overline)[0];
|
||
|
||
expect(parse.type).toEqual("overline");
|
||
});
|
||
});
|
||
|
||
describe("An lap parser", function() {
|
||
it("should not fail on a text argument", function() {
|
||
expect("\\rlap{\\,/}{=}").toParse();
|
||
expect("\\mathrlap{\\,/}{=}").toParse();
|
||
expect("{=}\\llap{/\\,}").toParse();
|
||
expect("{=}\\mathllap{/\\,}").toParse();
|
||
expect("\\sum_{\\clap{ABCDEFG}}").toParse();
|
||
expect("\\sum_{\\mathclap{ABCDEFG}}").toParse();
|
||
});
|
||
|
||
it("should not fail if math version is used", function() {
|
||
expect("\\mathrlap{\\frac{a}{b}}{=}").toParse();
|
||
expect("{=}\\mathllap{\\frac{a}{b}}").toParse();
|
||
expect("\\sum_{\\mathclap{\\frac{a}{b}}}").toParse();
|
||
});
|
||
|
||
it("should fail on math if AMS version is used", function() {
|
||
expect("\\rlap{\\frac{a}{b}}{=}").toNotParse();
|
||
expect("{=}\\llap{\\frac{a}{b}}").toNotParse();
|
||
expect("\\sum_{\\clap{\\frac{a}{b}}}").toNotParse();
|
||
});
|
||
|
||
it("should produce a lap", function() {
|
||
const parse = getParsed("\\mathrlap{\\,/}")[0];
|
||
|
||
expect(parse.type).toEqual("lap");
|
||
});
|
||
});
|
||
|
||
describe("A rule parser", function() {
|
||
const emRule = "\\rule{1em}{2em}";
|
||
const exRule = "\\rule{1ex}{2em}";
|
||
const badUnitRule = "\\rule{1au}{2em}";
|
||
const noNumberRule = "\\rule{1em}{em}";
|
||
const incompleteRule = "\\rule{1em}";
|
||
const hardNumberRule = "\\rule{ 01.24ex}{2.450 em }";
|
||
|
||
it("should not fail", function() {
|
||
expect(emRule).toParse();
|
||
expect(exRule).toParse();
|
||
});
|
||
|
||
it("should not parse invalid units", function() {
|
||
expect(badUnitRule).toNotParse();
|
||
|
||
expect(noNumberRule).toNotParse();
|
||
});
|
||
|
||
it("should not parse incomplete rules", function() {
|
||
expect(incompleteRule).toNotParse();
|
||
});
|
||
|
||
it("should produce a rule", function() {
|
||
const parse = getParsed(emRule)[0];
|
||
|
||
expect(parse.type).toEqual("rule");
|
||
});
|
||
|
||
it("should list the correct units", function() {
|
||
const emParse = getParsed(emRule)[0];
|
||
const exParse = getParsed(exRule)[0];
|
||
|
||
expect(emParse.value.width.unit).toEqual("em");
|
||
expect(emParse.value.height.unit).toEqual("em");
|
||
|
||
expect(exParse.value.width.unit).toEqual("ex");
|
||
expect(exParse.value.height.unit).toEqual("em");
|
||
});
|
||
|
||
it("should parse the number correctly", function() {
|
||
const hardNumberParse = getParsed(hardNumberRule)[0];
|
||
|
||
expect(hardNumberParse.value.width.number).toBeCloseTo(1.24);
|
||
expect(hardNumberParse.value.height.number).toBeCloseTo(2.45);
|
||
});
|
||
|
||
it("should parse negative sizes", function() {
|
||
const parse = getParsed("\\rule{-1em}{- 0.2em}")[0];
|
||
|
||
expect(parse.value.width.number).toBeCloseTo(-1);
|
||
expect(parse.value.height.number).toBeCloseTo(-0.2);
|
||
});
|
||
});
|
||
|
||
describe("A kern parser", function() {
|
||
const emKern = "\\kern{1em}";
|
||
const exKern = "\\kern{1ex}";
|
||
const muKern = "\\mkern{1mu}";
|
||
const abKern = "a\\kern{1em}b";
|
||
const badUnitRule = "\\kern{1au}";
|
||
const noNumberRule = "\\kern{em}";
|
||
|
||
it("should list the correct units", function() {
|
||
const emParse = getParsed(emKern)[0];
|
||
const exParse = getParsed(exKern)[0];
|
||
const muParse = getParsed(muKern)[0];
|
||
const abParse = getParsed(abKern)[1];
|
||
|
||
expect(emParse.value.dimension.unit).toEqual("em");
|
||
expect(exParse.value.dimension.unit).toEqual("ex");
|
||
expect(muParse.value.dimension.unit).toEqual("mu");
|
||
expect(abParse.value.dimension.unit).toEqual("em");
|
||
});
|
||
|
||
it("should not parse invalid units", function() {
|
||
expect(badUnitRule).toNotParse();
|
||
expect(noNumberRule).toNotParse();
|
||
});
|
||
|
||
it("should parse negative sizes", function() {
|
||
const parse = getParsed("\\kern{-1em}")[0];
|
||
expect(parse.value.dimension.number).toBeCloseTo(-1);
|
||
});
|
||
|
||
it("should parse positive sizes", function() {
|
||
const parse = getParsed("\\kern{+1em}")[0];
|
||
expect(parse.value.dimension.number).toBeCloseTo(1);
|
||
});
|
||
});
|
||
|
||
describe("A non-braced kern parser", function() {
|
||
const emKern = "\\kern1em";
|
||
const exKern = "\\kern 1 ex";
|
||
const muKern = "\\mkern 1mu";
|
||
const abKern1 = "a\\mkern1mub";
|
||
const abKern2 = "a\\mkern-1mub";
|
||
const abKern3 = "a\\mkern-1mu b";
|
||
const badUnitRule = "\\kern1au";
|
||
const noNumberRule = "\\kern em";
|
||
|
||
it("should list the correct units", function() {
|
||
const emParse = getParsed(emKern)[0];
|
||
const exParse = getParsed(exKern)[0];
|
||
const muParse = getParsed(muKern)[0];
|
||
const abParse1 = getParsed(abKern1)[1];
|
||
const abParse2 = getParsed(abKern2)[1];
|
||
const abParse3 = getParsed(abKern3)[1];
|
||
|
||
expect(emParse.value.dimension.unit).toEqual("em");
|
||
expect(exParse.value.dimension.unit).toEqual("ex");
|
||
expect(muParse.value.dimension.unit).toEqual("mu");
|
||
expect(abParse1.value.dimension.unit).toEqual("mu");
|
||
expect(abParse2.value.dimension.unit).toEqual("mu");
|
||
expect(abParse3.value.dimension.unit).toEqual("mu");
|
||
});
|
||
|
||
it("should parse elements on either side of a kern", function() {
|
||
const abParse1 = getParsed(abKern1);
|
||
const abParse2 = getParsed(abKern2);
|
||
const abParse3 = getParsed(abKern3);
|
||
|
||
expect(abParse1.length).toEqual(3);
|
||
expect(abParse1[0].value).toEqual("a");
|
||
expect(abParse1[2].value).toEqual("b");
|
||
expect(abParse2.length).toEqual(3);
|
||
expect(abParse2[0].value).toEqual("a");
|
||
expect(abParse2[2].value).toEqual("b");
|
||
expect(abParse3.length).toEqual(3);
|
||
expect(abParse3[0].value).toEqual("a");
|
||
expect(abParse3[2].value).toEqual("b");
|
||
});
|
||
|
||
it("should not parse invalid units", function() {
|
||
expect(badUnitRule).toNotParse();
|
||
expect(noNumberRule).toNotParse();
|
||
});
|
||
|
||
it("should parse negative sizes", function() {
|
||
const parse = getParsed("\\kern-1em")[0];
|
||
expect(parse.value.dimension.number).toBeCloseTo(-1);
|
||
});
|
||
|
||
it("should parse positive sizes", function() {
|
||
const parse = getParsed("\\kern+1em")[0];
|
||
expect(parse.value.dimension.number).toBeCloseTo(1);
|
||
});
|
||
|
||
it("should handle whitespace", function() {
|
||
const abKern = "a\\mkern\t-\r1 \n mu\nb";
|
||
const abParse = getParsed(abKern);
|
||
|
||
expect(abParse.length).toEqual(3);
|
||
expect(abParse[0].value).toEqual("a");
|
||
expect(abParse[1].value.dimension.unit).toEqual("mu");
|
||
expect(abParse[2].value).toEqual("b");
|
||
});
|
||
});
|
||
|
||
describe("A left/right parser", function() {
|
||
const normalLeftRight = "\\left( \\dfrac{x}{y} \\right)";
|
||
const emptyRight = "\\left( \\dfrac{x}{y} \\right.";
|
||
|
||
it("should not fail", function() {
|
||
expect(normalLeftRight).toParse();
|
||
});
|
||
|
||
it("should produce a leftright", function() {
|
||
const parse = getParsed(normalLeftRight)[0];
|
||
|
||
expect(parse.type).toEqual("leftright");
|
||
expect(parse.value.left).toEqual("(");
|
||
expect(parse.value.right).toEqual(")");
|
||
});
|
||
|
||
it("should error when it is mismatched", function() {
|
||
const unmatchedLeft = "\\left( \\dfrac{x}{y}";
|
||
const unmatchedRight = "\\dfrac{x}{y} \\right)";
|
||
|
||
expect(unmatchedLeft).toNotParse();
|
||
|
||
expect(unmatchedRight).toNotParse();
|
||
});
|
||
|
||
it("should error when braces are mismatched", function() {
|
||
const unmatched = "{ \\left( \\dfrac{x}{y} } \\right)";
|
||
expect(unmatched).toNotParse();
|
||
});
|
||
|
||
it("should error when non-delimiters are provided", function() {
|
||
const nonDelimiter = "\\left$ \\dfrac{x}{y} \\right)";
|
||
expect(nonDelimiter).toNotParse();
|
||
});
|
||
|
||
it("should parse the empty '.' delimiter", function() {
|
||
expect(emptyRight).toParse();
|
||
});
|
||
|
||
it("should parse the '.' delimiter with normal sizes", function() {
|
||
const normalEmpty = "\\Bigl .";
|
||
expect(normalEmpty).toParse();
|
||
});
|
||
|
||
it("should handle \\middle", function() {
|
||
const normalMiddle = "\\left( \\dfrac{x}{y} \\middle| \\dfrac{y}{z} \\right)";
|
||
expect(normalMiddle).toParse();
|
||
});
|
||
|
||
it("should handle multiple \\middles", function() {
|
||
const multiMiddle = "\\left( \\dfrac{x}{y} \\middle| \\dfrac{y}{z} \\middle/ \\dfrac{z}{q} \\right)";
|
||
expect(multiMiddle).toParse();
|
||
});
|
||
|
||
it("should handle nested \\middles", function() {
|
||
const nestedMiddle = "\\left( a^2 \\middle| \\left( b \\middle/ c \\right) \\right)";
|
||
expect(nestedMiddle).toParse();
|
||
});
|
||
|
||
it("should error when \\middle is not in \\left...\\right", function() {
|
||
const unmatchedMiddle = "(\\middle|\\dfrac{x}{y})";
|
||
expect(unmatchedMiddle).toNotParse();
|
||
});
|
||
});
|
||
|
||
describe("left/right builder", () => {
|
||
const cases = [
|
||
['\\left\\langle \\right\\rangle', '\\left< \\right>'],
|
||
['\\left\\langle \\right\\rangle', '\\left\u27e8 \\right\u27e9'],
|
||
];
|
||
|
||
for (const [actual, expected] of cases) {
|
||
it(`should build "${actual}" like "${expected}"`, () => {
|
||
expect(actual).toBuildLike(expected);
|
||
});
|
||
}
|
||
});
|
||
|
||
describe("A begin/end parser", function() {
|
||
|
||
it("should parse a simple environment", function() {
|
||
expect("\\begin{matrix}a&b\\\\c&d\\end{matrix}").toParse();
|
||
});
|
||
|
||
it("should parse an environment with argument", function() {
|
||
expect("\\begin{array}{cc}a&b\\\\c&d\\end{array}").toParse();
|
||
});
|
||
|
||
it("should parse an environment with hlines", function() {
|
||
expect("\\begin{matrix}\\hline a&b\\\\ \\hline c&d\\end{matrix}").toParse();
|
||
});
|
||
|
||
it("should error when name is mismatched", function() {
|
||
expect("\\begin{matrix}a&b\\\\c&d\\end{pmatrix}").toNotParse();
|
||
});
|
||
|
||
it("should error when commands are mismatched", function() {
|
||
expect("\\begin{matrix}a&b\\\\c&d\\right{pmatrix}").toNotParse();
|
||
});
|
||
|
||
it("should error when end is missing", function() {
|
||
expect("\\begin{matrix}a&b\\\\c&d").toNotParse();
|
||
});
|
||
|
||
it("should error when braces are mismatched", function() {
|
||
expect("{\\begin{matrix}a&b\\\\c&d}\\end{matrix}").toNotParse();
|
||
});
|
||
|
||
it("should cooperate with infix notation", function() {
|
||
expect("\\begin{matrix}0&1\\over2&3\\\\4&5&6\\end{matrix}").toParse();
|
||
});
|
||
|
||
it("should nest", function() {
|
||
const m1 = "\\begin{pmatrix}1&2\\\\3&4\\end{pmatrix}";
|
||
const m2 = "\\begin{array}{rl}" + m1 + "&0\\\\0&" + m1 + "\\end{array}";
|
||
expect(m2).toParse();
|
||
});
|
||
|
||
it("should allow \\cr as a line terminator", function() {
|
||
expect("\\begin{matrix}a&b\\cr c&d\\end{matrix}").toParse();
|
||
});
|
||
|
||
it("should eat a final newline", function() {
|
||
const m3 = getParsed("\\begin{matrix}a&b\\\\ c&d \\\\ \\end{matrix}")[0];
|
||
expect(m3.value.body.length).toBe(2);
|
||
});
|
||
});
|
||
|
||
describe("A sqrt parser", function() {
|
||
const sqrt = "\\sqrt{x}";
|
||
const missingGroup = "\\sqrt";
|
||
|
||
it("should parse square roots", function() {
|
||
expect(sqrt).toParse();
|
||
});
|
||
|
||
it("should error when there is no group", function() {
|
||
expect(missingGroup).toNotParse();
|
||
});
|
||
|
||
it("should produce sqrts", function() {
|
||
const parse = getParsed(sqrt)[0];
|
||
|
||
expect(parse.type).toEqual("sqrt");
|
||
});
|
||
});
|
||
|
||
describe("A TeX-compliant parser", function() {
|
||
it("should work", function() {
|
||
expect("\\frac 2 3").toParse();
|
||
});
|
||
|
||
it("should fail if there are not enough arguments", function() {
|
||
const missingGroups = [
|
||
"\\frac{x}",
|
||
"\\textcolor{#fff}",
|
||
"\\rule{1em}",
|
||
"\\llap",
|
||
"\\bigl",
|
||
"\\text",
|
||
];
|
||
|
||
for (let i = 0; i < missingGroups.length; i++) {
|
||
expect(missingGroups[i]).toNotParse();
|
||
}
|
||
});
|
||
|
||
it("should fail when there are missing sup/subscripts", function() {
|
||
expect("x^").toNotParse();
|
||
expect("x_").toNotParse();
|
||
});
|
||
|
||
it("should fail when arguments require arguments", function() {
|
||
const badArguments = [
|
||
"\\frac \\frac x y z",
|
||
"\\frac x \\frac y z",
|
||
"\\frac \\sqrt x y",
|
||
"\\frac x \\sqrt y",
|
||
"\\frac \\mathllap x y",
|
||
"\\frac x \\mathllap y",
|
||
// This actually doesn't work in real TeX, but it is suprisingly
|
||
// hard to get this to correctly work. So, we take hit of very small
|
||
// amounts of non-compatiblity in order for the rest of the tests to
|
||
// work
|
||
// "\\llap \\frac x y",
|
||
"\\mathllap \\mathllap x",
|
||
"\\sqrt \\mathllap x",
|
||
];
|
||
|
||
for (let i = 0; i < badArguments.length; i++) {
|
||
expect(badArguments[i]).toNotParse();
|
||
}
|
||
});
|
||
|
||
it("should work when the arguments have braces", function() {
|
||
const goodArguments = [
|
||
"\\frac {\\frac x y} z",
|
||
"\\frac x {\\frac y z}",
|
||
"\\frac {\\sqrt x} y",
|
||
"\\frac x {\\sqrt y}",
|
||
"\\frac {\\mathllap x} y",
|
||
"\\frac x {\\mathllap y}",
|
||
"\\mathllap {\\frac x y}",
|
||
"\\mathllap {\\mathllap x}",
|
||
"\\sqrt {\\mathllap x}",
|
||
];
|
||
|
||
for (let i = 0; i < goodArguments.length; i++) {
|
||
expect(goodArguments[i]).toParse();
|
||
}
|
||
});
|
||
|
||
it("should fail when sup/subscripts require arguments", function() {
|
||
const badSupSubscripts = [
|
||
"x^\\sqrt x",
|
||
"x^\\mathllap x",
|
||
"x_\\sqrt x",
|
||
"x_\\mathllap x",
|
||
];
|
||
|
||
for (let i = 0; i < badSupSubscripts.length; i++) {
|
||
expect(badSupSubscripts[i]).toNotParse();
|
||
}
|
||
});
|
||
|
||
it("should work when sup/subscripts arguments have braces", function() {
|
||
const goodSupSubscripts = [
|
||
"x^{\\sqrt x}",
|
||
"x^{\\mathllap x}",
|
||
"x_{\\sqrt x}",
|
||
"x_{\\mathllap x}",
|
||
];
|
||
|
||
for (let i = 0; i < goodSupSubscripts.length; i++) {
|
||
expect(goodSupSubscripts[i]).toParse();
|
||
}
|
||
});
|
||
|
||
it("should parse multiple primes correctly", function() {
|
||
expect("x''''").toParse();
|
||
expect("x_2''").toParse();
|
||
expect("x''_2").toParse();
|
||
});
|
||
|
||
it("should fail when sup/subscripts are interspersed with arguments", function() {
|
||
expect("\\sqrt^23").toNotParse();
|
||
expect("\\frac^234").toNotParse();
|
||
expect("\\frac2^34").toNotParse();
|
||
});
|
||
|
||
it("should succeed when sup/subscripts come after whole functions", function() {
|
||
expect("\\sqrt2^3").toParse();
|
||
expect("\\frac23^4").toParse();
|
||
});
|
||
|
||
it("should succeed with a sqrt around a text/frac", function() {
|
||
expect("\\sqrt \\frac x y").toParse();
|
||
expect("\\sqrt \\text x").toParse();
|
||
expect("x^\\frac x y").toParse();
|
||
expect("x_\\text x").toParse();
|
||
});
|
||
|
||
it("should fail when arguments are \\left", function() {
|
||
const badLeftArguments = [
|
||
"\\frac \\left( x \\right) y",
|
||
"\\frac x \\left( y \\right)",
|
||
"\\mathllap \\left( x \\right)",
|
||
"\\sqrt \\left( x \\right)",
|
||
"x^\\left( x \\right)",
|
||
];
|
||
|
||
for (let i = 0; i < badLeftArguments.length; i++) {
|
||
expect(badLeftArguments[i]).toNotParse();
|
||
}
|
||
});
|
||
|
||
it("should succeed when there are braces around the \\left/\\right", function() {
|
||
const goodLeftArguments = [
|
||
"\\frac {\\left( x \\right)} y",
|
||
"\\frac x {\\left( y \\right)}",
|
||
"\\mathllap {\\left( x \\right)}",
|
||
"\\sqrt {\\left( x \\right)}",
|
||
"x^{\\left( x \\right)}",
|
||
];
|
||
|
||
for (let i = 0; i < goodLeftArguments.length; i++) {
|
||
expect(goodLeftArguments[i]).toParse();
|
||
}
|
||
});
|
||
});
|
||
|
||
describe("A style change parser", function() {
|
||
it("should not fail", function() {
|
||
expect("\\displaystyle x").toParse();
|
||
expect("\\textstyle x").toParse();
|
||
expect("\\scriptstyle x").toParse();
|
||
expect("\\scriptscriptstyle x").toParse();
|
||
});
|
||
|
||
it("should produce the correct style", function() {
|
||
const displayParse = getParsed("\\displaystyle x")[0];
|
||
expect(displayParse.value.style).toEqual("display");
|
||
|
||
const scriptscriptParse = getParsed("\\scriptscriptstyle x")[0];
|
||
expect(scriptscriptParse.value.style).toEqual("scriptscript");
|
||
});
|
||
|
||
it("should only change the style within its group", function() {
|
||
const text = "a b { c d \\displaystyle e f } g h";
|
||
const parse = getParsed(text);
|
||
|
||
const displayNode = parse[2].value[2];
|
||
|
||
expect(displayNode.type).toEqual("styling");
|
||
|
||
const displayBody = displayNode.value.value;
|
||
|
||
expect(displayBody.length).toEqual(2);
|
||
expect(displayBody[0].value).toEqual("e");
|
||
});
|
||
});
|
||
|
||
describe("A font parser", function() {
|
||
it("should parse \\mathrm, \\mathbb, and \\mathit", function() {
|
||
expect("\\mathrm x").toParse();
|
||
expect("\\mathbb x").toParse();
|
||
expect("\\mathit x").toParse();
|
||
expect("\\mathrm {x + 1}").toParse();
|
||
expect("\\mathbb {x + 1}").toParse();
|
||
expect("\\mathit {x + 1}").toParse();
|
||
});
|
||
|
||
it("should parse \\mathcal and \\mathfrak", function() {
|
||
expect("\\mathcal{ABC123}").toParse();
|
||
expect("\\mathfrak{abcABC123}").toParse();
|
||
});
|
||
|
||
it("should produce the correct fonts", function() {
|
||
const mathbbParse = getParsed("\\mathbb x")[0];
|
||
expect(mathbbParse.value.font).toEqual("mathbb");
|
||
expect(mathbbParse.value.type).toEqual("font");
|
||
|
||
const mathrmParse = getParsed("\\mathrm x")[0];
|
||
expect(mathrmParse.value.font).toEqual("mathrm");
|
||
expect(mathrmParse.value.type).toEqual("font");
|
||
|
||
const mathitParse = getParsed("\\mathit x")[0];
|
||
expect(mathitParse.value.font).toEqual("mathit");
|
||
expect(mathitParse.value.type).toEqual("font");
|
||
|
||
const mathcalParse = getParsed("\\mathcal C")[0];
|
||
expect(mathcalParse.value.font).toEqual("mathcal");
|
||
expect(mathcalParse.value.type).toEqual("font");
|
||
|
||
const mathfrakParse = getParsed("\\mathfrak C")[0];
|
||
expect(mathfrakParse.value.font).toEqual("mathfrak");
|
||
expect(mathfrakParse.value.type).toEqual("font");
|
||
});
|
||
|
||
it("should parse nested font commands", function() {
|
||
const nestedParse = getParsed("\\mathbb{R \\neq \\mathrm{R}}")[0];
|
||
expect(nestedParse.value.font).toEqual("mathbb");
|
||
expect(nestedParse.value.type).toEqual("font");
|
||
|
||
expect(nestedParse.value.body.value.length).toEqual(3);
|
||
const bbBody = nestedParse.value.body.value;
|
||
expect(bbBody[0].type).toEqual("mathord");
|
||
expect(bbBody[1].type).toEqual("rel");
|
||
expect(bbBody[2].type).toEqual("font");
|
||
expect(bbBody[2].value.font).toEqual("mathrm");
|
||
expect(bbBody[2].value.type).toEqual("font");
|
||
});
|
||
|
||
it("should work with \\textcolor", function() {
|
||
const colorMathbbParse = getParsed("\\textcolor{blue}{\\mathbb R}")[0];
|
||
expect(colorMathbbParse.value.type).toEqual("color");
|
||
expect(colorMathbbParse.value.color).toEqual("blue");
|
||
const body = colorMathbbParse.value.value;
|
||
expect(body.length).toEqual(1);
|
||
expect(body[0].value.type).toEqual("font");
|
||
expect(body[0].value.font).toEqual("mathbb");
|
||
});
|
||
|
||
it("should not parse a series of font commands", function() {
|
||
expect("\\mathbb \\mathrm R").toNotParse();
|
||
});
|
||
|
||
it("should nest fonts correctly", function() {
|
||
const bf = getParsed("\\mathbf{a\\mathrm{b}c}")[0];
|
||
expect(bf.value.type).toEqual("font");
|
||
expect(bf.value.font).toEqual("mathbf");
|
||
expect(bf.value.body.value.length).toEqual(3);
|
||
expect(bf.value.body.value[0].value).toEqual("a");
|
||
expect(bf.value.body.value[1].value.type).toEqual("font");
|
||
expect(bf.value.body.value[1].value.font).toEqual("mathrm");
|
||
expect(bf.value.body.value[2].value).toEqual("c");
|
||
});
|
||
|
||
it("should have the correct greediness", function() {
|
||
expect("e^\\mathbf{x}").toParse();
|
||
});
|
||
});
|
||
|
||
describe("A comment parser", function() {
|
||
it("should parse comments at the end of a line", () => {
|
||
expect("a^2 + b^2 = c^2 % Pythagoras' Theorem\n").toParse();
|
||
});
|
||
|
||
it("should parse comments at the start of a line", () => {
|
||
expect("% comment\n").toParse();
|
||
});
|
||
|
||
it("should parse multiple lines of comments in a row", () => {
|
||
expect("% comment 1\n% comment 2\n").toParse();
|
||
});
|
||
|
||
it("should not parse a comment that isn't followed by a newline", () => {
|
||
expect("x%y").toNotParse();
|
||
});
|
||
|
||
it("should not produce or consume space", () => {
|
||
expect("\text{hello% comment 1\nworld}")
|
||
.toParseLike("\text{helloworld}");
|
||
expect("\text{hello% comment\n\nworld}")
|
||
.toParseLike("\text{hello world}");
|
||
});
|
||
|
||
it("should not include comments in the output", () => {
|
||
expect("5 % comment\n").toParseLike("5");
|
||
});
|
||
});
|
||
|
||
describe("An HTML font tree-builder", function() {
|
||
it("should render \\mathbb{R} with the correct font", function() {
|
||
const markup = katex.renderToString("\\mathbb{R}");
|
||
expect(markup).toContain("<span class=\"mord mathbb\">R</span>");
|
||
});
|
||
|
||
it("should render \\mathrm{R} with the correct font", function() {
|
||
const markup = katex.renderToString("\\mathrm{R}");
|
||
expect(markup).toContain("<span class=\"mord mathrm\">R</span>");
|
||
});
|
||
|
||
it("should render \\mathcal{R} with the correct font", function() {
|
||
const markup = katex.renderToString("\\mathcal{R}");
|
||
expect(markup).toContain("<span class=\"mord mathcal\">R</span>");
|
||
});
|
||
|
||
it("should render \\mathfrak{R} with the correct font", function() {
|
||
const markup = katex.renderToString("\\mathfrak{R}");
|
||
expect(markup).toContain("<span class=\"mord mathfrak\">R</span>");
|
||
});
|
||
|
||
it("should render \\text{R} with the correct font", function() {
|
||
const markup = katex.renderToString("\\text{R}");
|
||
expect(markup).toContain("<span class=\"mord\">R</span>");
|
||
});
|
||
|
||
it("should render \\textit{R} with the correct font", function() {
|
||
const markup = katex.renderToString("\\textit{R}");
|
||
expect(markup).toContain("<span class=\"mord textit\">R</span>");
|
||
});
|
||
|
||
it("should render \\text{\\textit{R}} with the correct font", function() {
|
||
const markup = katex.renderToString("\\text{\\textit{R}}");
|
||
expect(markup).toContain("<span class=\"mord textit\">R</span>");
|
||
});
|
||
|
||
it("should render \\text{R\\textit{S}T} with the correct fonts", function() {
|
||
const markup = katex.renderToString("\\text{R\\textit{S}T}");
|
||
expect(markup).toContain("<span class=\"mord\">R</span>");
|
||
expect(markup).toContain("<span class=\"mord textit\">S</span>");
|
||
expect(markup).toContain("<span class=\"mord\">T</span>");
|
||
});
|
||
|
||
it("should render \\textbf{R} with the correct font", function() {
|
||
const markup = katex.renderToString("\\textbf{R}");
|
||
expect(markup).toContain("<span class=\"mord textbf\">R</span>");
|
||
});
|
||
|
||
it("should render \\textsf{R} with the correct font", function() {
|
||
const markup = katex.renderToString("\\textsf{R}");
|
||
expect(markup).toContain("<span class=\"mord textsf\">R</span>");
|
||
});
|
||
|
||
it("should render \\textsf{\\textit{R}G\\textbf{B}} with the correct font", function() {
|
||
const markup = katex.renderToString("\\textsf{\\textit{R}G\\textbf{B}}");
|
||
expect(markup).toContain("<span class=\"mord textsf textit\">R</span>");
|
||
expect(markup).toContain("<span class=\"mord textsf\">G</span>");
|
||
expect(markup).toContain("<span class=\"mord textsf textbf\">B</span>");
|
||
});
|
||
|
||
it("should render \\textsf{\\textbf{$\\mathrm{A}$}} with the correct font", function() {
|
||
const markup = katex.renderToString("\\textsf{\\textbf{$\\mathrm{A}$}}");
|
||
expect(markup).toContain("<span class=\"mord mathrm\">A</span>");
|
||
});
|
||
|
||
it("should render \\textsf{\\textbf{$\\mathrm{\\textsf{A}}$}} with the correct font", function() {
|
||
const markup = katex.renderToString("\\textsf{\\textbf{$\\mathrm{\\textsf{A}}$}}");
|
||
expect(markup).toContain("<span class=\"mord textsf textbf\">A</span>");
|
||
});
|
||
|
||
it("should render \\texttt{R} with the correct font", function() {
|
||
const markup = katex.renderToString("\\texttt{R}");
|
||
expect(markup).toContain("<span class=\"mord texttt\">R</span>");
|
||
});
|
||
|
||
it("should render a combination of font and color changes", function() {
|
||
let markup = katex.renderToString("\\textcolor{blue}{\\mathbb R}");
|
||
let span = "<span class=\"mord mathbb\" style=\"color:blue;\">R</span>";
|
||
expect(markup).toContain(span);
|
||
|
||
markup = katex.renderToString("\\mathbb{\\textcolor{blue}{R}}");
|
||
span = "<span class=\"mord mathbb\" style=\"color:blue;\">R</span>";
|
||
expect(markup).toContain(span);
|
||
});
|
||
|
||
it("should throw TypeError when the expression is of the wrong type", function() {
|
||
expect(function() {
|
||
katex.renderToString({badInputType: "yes"});
|
||
}).toThrowError(TypeError);
|
||
expect(function() {
|
||
katex.renderToString([1, 2]);
|
||
}).toThrowError(TypeError);
|
||
expect(function() {
|
||
katex.renderToString(undefined);
|
||
}).toThrowError(TypeError);
|
||
expect(function() {
|
||
katex.renderToString(null);
|
||
}).toThrowError(TypeError);
|
||
expect(function() {
|
||
katex.renderToString(1.234);
|
||
}).toThrowError(TypeError);
|
||
});
|
||
|
||
it("should not throw TypeError when the expression is a supported type", function() {
|
||
expect(function() {
|
||
katex.renderToString("\\sqrt{123}");
|
||
}).not.toThrowError(TypeError);
|
||
expect(function() {
|
||
katex.renderToString(new String("\\sqrt{123}"));
|
||
}).not.toThrowError(TypeError);
|
||
});
|
||
});
|
||
|
||
|
||
describe("A MathML font tree-builder", function() {
|
||
const contents = "Ax2k\\omega\\Omega\\imath+";
|
||
|
||
it("should render " + contents + " with the correct mathvariants", function() {
|
||
const tree = getParsed(contents);
|
||
const markup = buildMathML(tree, contents, defaultOptions).toMarkup();
|
||
expect(markup).toContain("<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() {
|
||
const tex = "\\mathbb{" + contents + "}";
|
||
const tree = getParsed(tex);
|
||
const 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>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() {
|
||
const tex = "\\mathrm{" + contents + "}";
|
||
const tree = getParsed(tex);
|
||
const 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>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() {
|
||
const tex = "\\mathit{" + contents + "}";
|
||
const tree = getParsed(tex);
|
||
const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
|
||
expect(markup).toContain("<mi>A</mi>");
|
||
expect(markup).toContain("<mi>x</mi>");
|
||
expect(markup).toContain("<mn mathvariant=\"italic\">2</mn>");
|
||
expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
|
||
expect(markup).toContain("<mi>\u03A9</mi>"); // \Omega
|
||
expect(markup).toContain("<mi>\u0131</mi>"); // \imath
|
||
expect(markup).toContain("<mo>+</mo>");
|
||
});
|
||
|
||
it("should render \\mathbf{" + contents + "} with the correct mathvariants", function() {
|
||
const tex = "\\mathbf{" + contents + "}";
|
||
const tree = getParsed(tex);
|
||
const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
|
||
expect(markup).toContain("<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() {
|
||
const tex = "\\mathcal{" + contents + "}";
|
||
const tree = getParsed(tex);
|
||
const 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() {
|
||
const tex = "\\mathfrak{" + contents + "}";
|
||
const tree = getParsed(tex);
|
||
const 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() {
|
||
const tex = "\\mathscr{" + contents + "}";
|
||
const tree = getParsed(tex);
|
||
const 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>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() {
|
||
const tex = "\\mathsf{" + contents + "}";
|
||
const tree = getParsed(tex);
|
||
const 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() {
|
||
let tex = "\\textcolor{blue}{\\mathbb R}";
|
||
let tree = getParsed(tex);
|
||
let markup = buildMathML(tree, tex, defaultOptions).toMarkup();
|
||
let node = "<mstyle mathcolor=\"blue\">" +
|
||
"<mi mathvariant=\"double-struck\">R</mi>" +
|
||
"</mstyle>";
|
||
expect(markup).toContain(node);
|
||
|
||
// reverse the order of the commands
|
||
tex = "\\mathbb{\\textcolor{blue}{R}}";
|
||
tree = getParsed(tex);
|
||
markup = buildMathML(tree, tex, defaultOptions).toMarkup();
|
||
node = "<mstyle mathcolor=\"blue\">" +
|
||
"<mi mathvariant=\"double-struck\">R</mi>" +
|
||
"</mstyle>";
|
||
expect(markup).toContain(node);
|
||
});
|
||
|
||
it("should render text as <mtext>", function() {
|
||
const tex = "\\text{for }";
|
||
const tree = getParsed(tex);
|
||
const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
|
||
expect(markup).toContain("<mtext>for\u00a0</mtext>");
|
||
});
|
||
|
||
it("should render math within text as side-by-side children", function() {
|
||
const tex = "\\text{graph: $y = mx + b$}";
|
||
const tree = getParsed(tex);
|
||
const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
|
||
expect(markup).toContain("<mrow><mtext>graph:\u00a0</mtext>");
|
||
expect(markup).toContain(
|
||
"<mi>y</mi><mo>=</mo><mi>m</mi><mi>x</mi><mo>+</mo><mi>b</mi>");
|
||
});
|
||
});
|
||
|
||
describe("A bin builder", function() {
|
||
it("should create mbins normally", function() {
|
||
const built = getBuilt("x + y");
|
||
|
||
// we add glue elements around the '+'
|
||
expect(built[2].classes).toContain("mbin");
|
||
});
|
||
|
||
it("should create ords when at the beginning of lists", function() {
|
||
const built = getBuilt("+ x");
|
||
|
||
expect(built[0].classes).toContain("mord");
|
||
expect(built[0].classes).not.toContain("mbin");
|
||
});
|
||
|
||
it("should create ords after some other objects", function() {
|
||
expect(getBuilt("x + + 2")[4].classes).toContain("mord");
|
||
expect(getBuilt("( + 2")[2].classes).toContain("mord");
|
||
expect(getBuilt("= + 2")[2].classes).toContain("mord");
|
||
expect(getBuilt("\\sin + 2")[2].classes).toContain("mord");
|
||
expect(getBuilt(", + 2")[2].classes).toContain("mord");
|
||
});
|
||
|
||
it("should correctly interact with color objects", function() {
|
||
expect(getBuilt("\\blue{x}+y")[2].classes).toContain("mbin");
|
||
expect(getBuilt("\\blue{x+}+y")[2].classes).toContain("mbin");
|
||
expect(getBuilt("\\blue{x+}+y")[4].classes).toContain("mord");
|
||
});
|
||
});
|
||
|
||
describe("A markup generator", function() {
|
||
it("marks trees up", function() {
|
||
// Just a few quick sanity checks here...
|
||
const markup = katex.renderToString("\\sigma^2");
|
||
expect(markup.indexOf("<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() {
|
||
const markup = katex.renderToString("a");
|
||
|
||
expect(markup).toContain("<span");
|
||
expect(markup).toContain("<math");
|
||
});
|
||
});
|
||
|
||
describe("A parse tree generator", function() {
|
||
it("generates a tree", function() {
|
||
const tree = stripPositions(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() {
|
||
const parse = getParsed("\\vec x")[0];
|
||
|
||
expect(parse.type).toEqual("accent");
|
||
});
|
||
|
||
it("should be grouped more tightly than supsubs", function() {
|
||
const parse = getParsed("\\vec x^2")[0];
|
||
|
||
expect(parse.type).toEqual("supsub");
|
||
});
|
||
|
||
it("should parse stretchy, shifty accents", function() {
|
||
expect("\\widehat{x}").toParse();
|
||
});
|
||
|
||
it("should parse stretchy, non-shifty accents", function() {
|
||
expect("\\overrightarrow{x}").toParse();
|
||
});
|
||
});
|
||
|
||
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 stretchy and shifty accent builder", function() {
|
||
it("should not fail", function() {
|
||
expect("\\widehat{AB}").toBuild();
|
||
expect("\\widehat{AB}^2").toBuild();
|
||
expect("\\widehat{AB}_2").toBuild();
|
||
expect("\\widehat{AB}_2^2").toBuild();
|
||
});
|
||
|
||
it("should produce mords", function() {
|
||
expect(getBuilt("\\widehat{AB}")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\widehat +")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\widehat +")[0].classes).not.toContain("mbin");
|
||
expect(getBuilt("\\widehat )^2")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\widehat )^2")[0].classes).not.toContain("mclose");
|
||
});
|
||
});
|
||
|
||
describe("A stretchy and non-shifty accent builder", function() {
|
||
it("should not fail", function() {
|
||
expect("\\overrightarrow{AB}").toBuild();
|
||
expect("\\overrightarrow{AB}^2").toBuild();
|
||
expect("\\overrightarrow{AB}_2").toBuild();
|
||
expect("\\overrightarrow{AB}_2^2").toBuild();
|
||
});
|
||
|
||
it("should produce mords", function() {
|
||
expect(getBuilt("\\overrightarrow{AB}")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\overrightarrow +")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\overrightarrow +")[0].classes).not.toContain("mbin");
|
||
expect(getBuilt("\\overrightarrow )^2")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\overrightarrow )^2")[0].classes).not.toContain("mclose");
|
||
});
|
||
});
|
||
|
||
describe("An under-accent parser", function() {
|
||
it("should not fail", function() {
|
||
expect("\\underrightarrow{x}").toParse();
|
||
expect("\\underrightarrow{x^2}").toParse();
|
||
expect("\\underrightarrow{x}^2").toParse();
|
||
expect("\\underrightarrow x").toParse();
|
||
});
|
||
|
||
it("should produce accentUnder", function() {
|
||
const parse = getParsed("\\underrightarrow x")[0];
|
||
|
||
expect(parse.type).toEqual("accentUnder");
|
||
});
|
||
|
||
it("should be grouped more tightly than supsubs", function() {
|
||
const parse = getParsed("\\underrightarrow x^2")[0];
|
||
|
||
expect(parse.type).toEqual("supsub");
|
||
});
|
||
});
|
||
|
||
describe("An under-accent builder", function() {
|
||
it("should not fail", function() {
|
||
expect("\\underrightarrow{x}").toBuild();
|
||
expect("\\underrightarrow{x}^2").toBuild();
|
||
expect("\\underrightarrow{x}_2").toBuild();
|
||
expect("\\underrightarrow{x}_2^2").toBuild();
|
||
});
|
||
|
||
it("should produce mords", function() {
|
||
expect(getBuilt("\\underrightarrow x")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\underrightarrow +")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\underrightarrow +")[0].classes).not.toContain("mbin");
|
||
expect(getBuilt("\\underrightarrow )^2")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\underrightarrow )^2")[0].classes).not.toContain("mclose");
|
||
});
|
||
});
|
||
|
||
describe("An extensible arrow parser", function() {
|
||
it("should not fail", function() {
|
||
expect("\\xrightarrow{x}").toParse();
|
||
expect("\\xrightarrow{x^2}").toParse();
|
||
expect("\\xrightarrow{x}^2").toParse();
|
||
expect("\\xrightarrow x").toParse();
|
||
expect("\\xrightarrow[under]{over}").toParse();
|
||
});
|
||
|
||
it("should produce xArrow", function() {
|
||
const parse = getParsed("\\xrightarrow x")[0];
|
||
|
||
expect(parse.type).toEqual("xArrow");
|
||
});
|
||
|
||
it("should be grouped more tightly than supsubs", function() {
|
||
const parse = getParsed("\\xrightarrow x^2")[0];
|
||
|
||
expect(parse.type).toEqual("supsub");
|
||
});
|
||
});
|
||
|
||
describe("An extensible arrow builder", function() {
|
||
it("should not fail", function() {
|
||
expect("\\xrightarrow{x}").toBuild();
|
||
expect("\\xrightarrow{x}^2").toBuild();
|
||
expect("\\xrightarrow{x}_2").toBuild();
|
||
expect("\\xrightarrow{x}_2^2").toBuild();
|
||
expect("\\xrightarrow[under]{over}").toBuild();
|
||
});
|
||
|
||
it("should produce mrell", function() {
|
||
expect(getBuilt("\\xrightarrow x")[0].classes).toContain("mrel");
|
||
expect(getBuilt("\\xrightarrow [under]{over}")[0].classes).toContain("mrel");
|
||
expect(getBuilt("\\xrightarrow +")[0].classes).toContain("mrel");
|
||
expect(getBuilt("\\xrightarrow +")[0].classes).not.toContain("mbin");
|
||
expect(getBuilt("\\xrightarrow )^2")[0].classes).toContain("mrel");
|
||
expect(getBuilt("\\xrightarrow )^2")[0].classes).not.toContain("mclose");
|
||
});
|
||
});
|
||
|
||
describe("A horizontal brace parser", function() {
|
||
it("should not fail", function() {
|
||
expect("\\overbrace{x}").toParse();
|
||
expect("\\overbrace{x^2}").toParse();
|
||
expect("\\overbrace{x}^2").toParse();
|
||
expect("\\overbrace x").toParse();
|
||
expect("\\underbrace{x}_2").toParse();
|
||
expect("\\underbrace{x}_2^2").toParse();
|
||
});
|
||
|
||
it("should produce horizBrace", function() {
|
||
const parse = getParsed("\\overbrace x")[0];
|
||
|
||
expect(parse.type).toEqual("horizBrace");
|
||
});
|
||
|
||
it("should be grouped more tightly than supsubs", function() {
|
||
const parse = getParsed("\\overbrace x^2")[0];
|
||
|
||
expect(parse.type).toEqual("supsub");
|
||
});
|
||
});
|
||
|
||
describe("A horizontal brace builder", function() {
|
||
it("should not fail", function() {
|
||
expect("\\overbrace{x}").toBuild();
|
||
expect("\\overbrace{x}^2").toBuild();
|
||
expect("\\underbrace{x}_2").toBuild();
|
||
expect("\\underbrace{x}_2^2").toBuild();
|
||
});
|
||
|
||
it("should produce mords", function() {
|
||
expect(getBuilt("\\overbrace x")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\overbrace{x}^2")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\overbrace +")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\overbrace +")[0].classes).not.toContain("mbin");
|
||
expect(getBuilt("\\overbrace )^2")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\overbrace )^2")[0].classes).not.toContain("mclose");
|
||
});
|
||
});
|
||
|
||
describe("A boxed parser", function() {
|
||
it("should not fail", function() {
|
||
expect("\\boxed{x}").toParse();
|
||
expect("\\boxed{x^2}").toParse();
|
||
expect("\\boxed{x}^2").toParse();
|
||
expect("\\boxed x").toParse();
|
||
});
|
||
|
||
it("should produce enclose", function() {
|
||
const parse = getParsed("\\boxed x")[0];
|
||
|
||
expect(parse.type).toEqual("enclose");
|
||
});
|
||
});
|
||
|
||
describe("A boxed builder", function() {
|
||
it("should not fail", function() {
|
||
expect("\\boxed{x}").toBuild();
|
||
expect("\\boxed{x}^2").toBuild();
|
||
expect("\\boxed{x}_2").toBuild();
|
||
expect("\\boxed{x}_2^2").toBuild();
|
||
});
|
||
|
||
it("should produce mords", function() {
|
||
expect(getBuilt("\\boxed x")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\boxed +")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\boxed +")[0].classes).not.toContain("mbin");
|
||
expect(getBuilt("\\boxed )^2")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\boxed )^2")[0].classes).not.toContain("mclose");
|
||
});
|
||
});
|
||
|
||
describe("A colorbox parser", function() {
|
||
it("should not fail, given a text argument", function() {
|
||
expect("\\colorbox{red}{a b}").toParse();
|
||
expect("\\colorbox{red}{x}^2").toParse();
|
||
expect("\\colorbox{red} x").toParse();
|
||
});
|
||
|
||
it("should fail, given a math argument", function() {
|
||
expect("\\colorbox{red}{\\alpha}").toNotParse();
|
||
expect("\\colorbox{red}{\\frac{a}{b}}").toNotParse();
|
||
});
|
||
|
||
it("should parse a color", function() {
|
||
expect("\\colorbox{red}{a b}").toParse();
|
||
expect("\\colorbox{#197}{a b}").toParse();
|
||
expect("\\colorbox{#1a9b7c}{a b}").toParse();
|
||
});
|
||
|
||
it("should produce enclose", function() {
|
||
const parse = getParsed("\\colorbox{red} x")[0];
|
||
expect(parse.type).toEqual("enclose");
|
||
});
|
||
});
|
||
|
||
describe("A colorbox builder", function() {
|
||
it("should not fail", function() {
|
||
expect("\\colorbox{red}{a b}").toBuild();
|
||
expect("\\colorbox{red}{a b}^2").toBuild();
|
||
expect("\\colorbox{red} x").toBuild();
|
||
});
|
||
|
||
it("should produce mords", function() {
|
||
expect(getBuilt("\\colorbox{red}{a b}")[0].classes).toContain("mord");
|
||
});
|
||
});
|
||
|
||
describe("An fcolorbox parser", function() {
|
||
it("should not fail, given a text argument", function() {
|
||
expect("\\fcolorbox{blue}{yellow}{a b}").toParse();
|
||
expect("\\fcolorbox{blue}{yellow}{x}^2").toParse();
|
||
expect("\\fcolorbox{blue}{yellow} x").toParse();
|
||
});
|
||
|
||
it("should fail, given a math argument", function() {
|
||
expect("\\fcolorbox{blue}{yellow}{\\alpha}").toNotParse();
|
||
expect("\\fcolorbox{blue}{yellow}{\\frac{a}{b}}").toNotParse();
|
||
});
|
||
|
||
it("should parse a color", function() {
|
||
expect("\\fcolorbox{blue}{yellow}{a b}").toParse();
|
||
expect("\\fcolorbox{blue}{#197}{a b}").toParse();
|
||
expect("\\fcolorbox{blue}{#1a9b7c}{a b}").toParse();
|
||
});
|
||
|
||
it("should produce enclose", function() {
|
||
const parse = getParsed("\\fcolorbox{blue}{yellow} x")[0];
|
||
expect(parse.type).toEqual("enclose");
|
||
});
|
||
});
|
||
|
||
describe("A fcolorbox builder", function() {
|
||
it("should not fail", function() {
|
||
expect("\\fcolorbox{blue}{yellow}{a b}").toBuild();
|
||
expect("\\fcolorbox{blue}{yellow}{a b}^2").toBuild();
|
||
expect("\\fcolorbox{blue}{yellow} x").toBuild();
|
||
});
|
||
|
||
it("should produce mords", function() {
|
||
expect(getBuilt("\\colorbox{red}{a b}")[0].classes).toContain("mord");
|
||
});
|
||
});
|
||
|
||
describe("A strike-through parser", function() {
|
||
it("should not fail", function() {
|
||
expect("\\cancel{x}").toParse();
|
||
expect("\\cancel{x^2}").toParse();
|
||
expect("\\cancel{x}^2").toParse();
|
||
expect("\\cancel x").toParse();
|
||
});
|
||
|
||
it("should produce enclose", function() {
|
||
const parse = getParsed("\\cancel x")[0];
|
||
|
||
expect(parse.type).toEqual("enclose");
|
||
});
|
||
|
||
it("should be grouped more tightly than supsubs", function() {
|
||
const parse = getParsed("\\cancel x^2")[0];
|
||
|
||
expect(parse.type).toEqual("supsub");
|
||
});
|
||
});
|
||
|
||
describe("A strike-through builder", function() {
|
||
it("should not fail", function() {
|
||
expect("\\cancel{x}").toBuild();
|
||
expect("\\cancel{x}^2").toBuild();
|
||
expect("\\cancel{x}_2").toBuild();
|
||
expect("\\cancel{x}_2^2").toBuild();
|
||
expect("\\sout{x}").toBuild();
|
||
expect("\\sout{x}^2").toBuild();
|
||
expect("\\sout{x}_2").toBuild();
|
||
expect("\\sout{x}_2^2").toBuild();
|
||
});
|
||
|
||
it("should produce mords", function() {
|
||
expect(getBuilt("\\cancel x")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\cancel +")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\cancel +")[0].classes).not.toContain("mbin");
|
||
expect(getBuilt("\\cancel )^2")[0].classes).toContain("mord");
|
||
expect(getBuilt("\\cancel )^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();
|
||
expect("\\hphantom{x}").toParse();
|
||
expect("\\hphantom{x^2}").toParse();
|
||
expect("\\hphantom{x}^2").toParse();
|
||
expect("\\hphantom x").toParse();
|
||
});
|
||
|
||
it("should build a phantom node", function() {
|
||
const 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();
|
||
|
||
expect("\\hphantom{x}").toBuild();
|
||
expect("\\hphantom{x^2}").toBuild();
|
||
expect("\\hphantom{x}^2").toBuild();
|
||
expect("\\hphantom x").toBuild();
|
||
});
|
||
|
||
it("should make the children transparent", function() {
|
||
const children = getBuilt("\\phantom{x+1}");
|
||
expect(children[0].style.color).toBe("transparent");
|
||
expect(children[2].style.color).toBe("transparent");
|
||
expect(children[4].style.color).toBe("transparent");
|
||
});
|
||
|
||
it("should make all descendants transparent", function() {
|
||
const children = getBuilt("\\phantom{x+\\blue{1}}");
|
||
expect(children[0].style.color).toBe("transparent");
|
||
expect(children[2].style.color).toBe("transparent");
|
||
expect(children[4].style.color).toBe("transparent");
|
||
});
|
||
});
|
||
|
||
describe("A smash parser", function() {
|
||
it("should not fail", function() {
|
||
expect("\\smash{x}").toParse();
|
||
expect("\\smash{x^2}").toParse();
|
||
expect("\\smash{x}^2").toParse();
|
||
expect("\\smash x").toParse();
|
||
|
||
expect("\\smash[b]{x}").toParse();
|
||
expect("\\smash[b]{x^2}").toParse();
|
||
expect("\\smash[b]{x}^2").toParse();
|
||
expect("\\smash[b] x").toParse();
|
||
|
||
expect("\\smash[]{x}").toParse();
|
||
expect("\\smash[]{x^2}").toParse();
|
||
expect("\\smash[]{x}^2").toParse();
|
||
expect("\\smash[] x").toParse();
|
||
});
|
||
|
||
it("should build a smash node", function() {
|
||
const parse = getParsed("\\smash{x}")[0];
|
||
|
||
expect(parse.type).toEqual("smash");
|
||
});
|
||
});
|
||
|
||
describe("A smash builder", function() {
|
||
it("should not fail", function() {
|
||
expect("\\smash{x}").toBuild();
|
||
expect("\\smash{x^2}").toBuild();
|
||
expect("\\smash{x}^2").toBuild();
|
||
expect("\\smash x").toBuild();
|
||
|
||
expect("\\smash[b]{x}").toBuild();
|
||
expect("\\smash[b]{x^2}").toBuild();
|
||
expect("\\smash[b]{x}^2").toBuild();
|
||
expect("\\smash[b] x").toBuild();
|
||
});
|
||
});
|
||
|
||
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() {
|
||
const 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() {
|
||
const 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();
|
||
});
|
||
|
||
it("should allow cells in brackets", function() {
|
||
expect("\\begin{aligned}[a]&[b]\\\\ [c]&[d]\\end{aligned}")
|
||
.toParse();
|
||
});
|
||
|
||
it("should forbid cells in brackets without space", function() {
|
||
expect("\\begin{aligned}[a]&[b]\\\\[c]&[d]\\end{aligned}")
|
||
.toNotParse();
|
||
});
|
||
|
||
it("should not eat the last row when its first cell is empty", function() {
|
||
const ae = getParsed("\\begin{aligned}&E_1 & (1)\\\\&E_2 & (2)\\\\&E_3 & (3)\\end{aligned}")[0];
|
||
expect(ae.value.body.length).toBe(3);
|
||
});
|
||
});
|
||
|
||
describe("An href command", function() {
|
||
it("should parse its input", function() {
|
||
expect("\\href{http://example.com/}{example here}").toParse();
|
||
});
|
||
|
||
it("should allow letters [#$%&~_^] without escaping", function() {
|
||
const url = "http://example.org/~bar/#top?foo=$foo&bar=ba^r_boo%20baz";
|
||
const hash = getParsed(`\\href{${url}}{\\alpha}`)[0];
|
||
expect(hash.value.href).toBe(url);
|
||
});
|
||
|
||
it("should allow balanced braces in url", function() {
|
||
const url = "http://example.org/{too}";
|
||
const hash = getParsed(`\\href{${url}}{\\alpha}`)[0];
|
||
expect(hash.value.href).toBe(url);
|
||
});
|
||
|
||
it("should not allow unbalanced brace(s) in url", function() {
|
||
expect("\\href{http://example.com/{a}{bar}").toNotParse();
|
||
expect("\\href{http://example.com/}a}{bar}").toNotParse();
|
||
});
|
||
|
||
it("should allow escape for letters [#$%&~_^{}]", function() {
|
||
const url = "http://example.org/~bar/#top?foo=$}foo{&bar=bar^r_boo%20baz";
|
||
const input = url.replace(/([#$%&~_^{}])/g, '\\$1');
|
||
const ae = getParsed(`\\href{${input}}{\\alpha}`)[0];
|
||
expect(ae.value.href).toBe(url);
|
||
});
|
||
|
||
it("should be marked up correctly", function() {
|
||
const markup = katex.renderToString("\\href{http://example.com/}{example here}");
|
||
expect(markup).toContain("<a href=\"http://example.com/\">");
|
||
});
|
||
});
|
||
|
||
describe("A parser that does not throw on unsupported commands", function() {
|
||
// The parser breaks on unsupported commands unless it is explicitly
|
||
// told not to
|
||
const errorColor = "#933";
|
||
const noThrowSettings = new Settings({
|
||
throwOnError: false,
|
||
errorColor: errorColor,
|
||
});
|
||
|
||
it("should still parse on unrecognized control sequences", function() {
|
||
expect("\\error").toParse(noThrowSettings);
|
||
});
|
||
|
||
describe("should allow unrecognized controls sequences anywhere, including", function() {
|
||
it("in superscripts and subscripts", function() {
|
||
expect("2_\\error").toBuild(noThrowSettings);
|
||
expect("3^{\\error}_\\error").toBuild(noThrowSettings);
|
||
expect("\\int\\nolimits^\\error_\\error").toBuild(noThrowSettings);
|
||
});
|
||
|
||
it("in fractions", function() {
|
||
expect("\\frac{345}{\\error}").toBuild(noThrowSettings);
|
||
expect("\\frac\\error{\\error}").toBuild(noThrowSettings);
|
||
});
|
||
|
||
it("in square roots", function() {
|
||
expect("\\sqrt\\error").toBuild(noThrowSettings);
|
||
expect("\\sqrt{234\\error}").toBuild(noThrowSettings);
|
||
});
|
||
|
||
it("in text boxes", function() {
|
||
expect("\\text{\\error}").toBuild(noThrowSettings);
|
||
});
|
||
});
|
||
|
||
it("should produce color nodes with a color value given by errorColor", function() {
|
||
const parsedInput = getParsed("\\error", noThrowSettings);
|
||
expect(parsedInput[0].type).toBe("color");
|
||
expect(parsedInput[0].value.color).toBe(errorColor);
|
||
});
|
||
|
||
it("should build katex-error span for other type of KaTeX error", function() {
|
||
// Use _getBuilt instead of getBuilt to avoid calling expect...toParse
|
||
// and thus throwing parse error
|
||
const built = _getBuilt("2^2^2", noThrowSettings);
|
||
expect(built).toMatchSnapshot();
|
||
});
|
||
|
||
it("should properly escape LaTeX in errors", function() {
|
||
const html = katex.renderToString("2^&\"<>", noThrowSettings);
|
||
expect(html).toMatchSnapshot();
|
||
});
|
||
});
|
||
|
||
describe("The symbol table integrity", function() {
|
||
it("should treat certain symbols as synonyms", function() {
|
||
expect(getBuilt("<")).toEqual(getBuilt("\\lt"));
|
||
expect(getBuilt(">")).toEqual(getBuilt("\\gt"));
|
||
expect(getBuilt("\\left<\\frac{1}{x}\\right>"))
|
||
.toEqual(getBuilt("\\left\\lt\\frac{1}{x}\\right\\gt"));
|
||
});
|
||
});
|
||
|
||
describe("Symbols", function() {
|
||
it("should support AMS symbols in both text and math mode", function() {
|
||
// These text+math symbols are from Section 6 of
|
||
// http://mirrors.ctan.org/fonts/amsfonts/doc/amsfonts.pdf
|
||
const symbols = "\\yen\\checkmark\\circledR\\maltese";
|
||
expect(symbols).toBuild();
|
||
expect(`\\text{${symbols}}`).toBuild();
|
||
});
|
||
});
|
||
|
||
describe("A macro expander", function() {
|
||
|
||
const compareParseTree = function(actual, expected, macros) {
|
||
const settings = new Settings({macros: macros});
|
||
actual = stripPositions(parseTree(actual, settings));
|
||
expected = stripPositions(parseTree(expected, defaultSettings));
|
||
expect(actual).toEqual(expected);
|
||
};
|
||
|
||
it("should produce individual tokens", function() {
|
||
compareParseTree("e^\\foo", "e^1 23", {"\\foo": "123"});
|
||
});
|
||
|
||
it("should preserve leading spaces inside macro definition", function() {
|
||
compareParseTree("\\text{\\foo}", "\\text{ x}", {"\\foo": " x"});
|
||
});
|
||
|
||
it("should preserve leading spaces inside macro argument", function() {
|
||
compareParseTree("\\text{\\foo{ x}}", "\\text{ x}", {"\\foo": "#1"});
|
||
});
|
||
|
||
it("should ignore expanded spaces in math mode", function() {
|
||
compareParseTree("\\foo", "x", {"\\foo": " x"});
|
||
});
|
||
|
||
it("should consume spaces after control-word macro", function() {
|
||
compareParseTree("\\text{\\foo }", "\\text{x}", {"\\foo": "x"});
|
||
});
|
||
|
||
it("should consume spaces after macro with \\relax", function() {
|
||
compareParseTree("\\text{\\foo }", "\\text{}", {"\\foo": "\\relax"});
|
||
});
|
||
|
||
it("should consume spaces after \\relax", function() {
|
||
compareParseTree("\\text{\\relax }", "\\text{}");
|
||
});
|
||
|
||
it("should consume spaces after control-word function", function() {
|
||
compareParseTree("\\text{\\KaTeX }", "\\text{\\KaTeX}");
|
||
});
|
||
|
||
it("should preserve spaces after control-symbol macro", function() {
|
||
compareParseTree("\\text{\\% y}", "\\text{x y}", {"\\%": "x"});
|
||
});
|
||
|
||
it("should preserve spaces after control-symbol function", function() {
|
||
expect("\\text{\\' }").toParse();
|
||
});
|
||
|
||
it("should consume spaces between arguments", function() {
|
||
compareParseTree("\\text{\\foo 1 2}", "\\text{12end}", {"\\foo": "#1#2end"});
|
||
compareParseTree("\\text{\\foo {1} {2}}", "\\text{12end}", {"\\foo": "#1#2end"});
|
||
});
|
||
|
||
it("should allow for multiple expansion", function() {
|
||
compareParseTree("1\\foo2", "1aa2", {
|
||
"\\foo": "\\bar\\bar",
|
||
"\\bar": "a",
|
||
});
|
||
});
|
||
|
||
it("should allow for multiple expansion with argument", function() {
|
||
compareParseTree("1\\foo2", "12222", {
|
||
"\\foo": "\\bar{#1}\\bar{#1}",
|
||
"\\bar": "#1#1",
|
||
});
|
||
});
|
||
|
||
it("should allow for macro argument", function() {
|
||
compareParseTree("\\foo\\bar", "(x)", {
|
||
"\\foo": "(#1)",
|
||
"\\bar": "x",
|
||
});
|
||
});
|
||
|
||
it("should allow for space macro argument (text version)", function() {
|
||
compareParseTree("\\text{\\foo\\bar}", "\\text{( )}", {
|
||
"\\foo": "(#1)",
|
||
"\\bar": " ",
|
||
});
|
||
});
|
||
|
||
it("should allow for space macro argument (math version)", function() {
|
||
compareParseTree("\\foo\\bar", "()", {
|
||
"\\foo": "(#1)",
|
||
"\\bar": " ",
|
||
});
|
||
});
|
||
|
||
it("should allow for space second argument (text version)", function() {
|
||
compareParseTree("\\text{\\foo\\bar\\bar}", "\\text{( , )}", {
|
||
"\\foo": "(#1,#2)",
|
||
"\\bar": " ",
|
||
});
|
||
});
|
||
|
||
it("should allow for space second argument (math version)", function() {
|
||
compareParseTree("\\foo\\bar\\bar", "(,)", {
|
||
"\\foo": "(#1,#2)",
|
||
"\\bar": " ",
|
||
});
|
||
});
|
||
|
||
it("should allow for empty macro argument", function() {
|
||
compareParseTree("\\foo\\bar", "()", {
|
||
"\\foo": "(#1)",
|
||
"\\bar": "",
|
||
});
|
||
});
|
||
|
||
// TODO: The following is not currently possible to get working, given that
|
||
// functions and macros are dealt with separately.
|
||
/*
|
||
it("should allow for space function arguments", function() {
|
||
compareParseTree("\\frac\\bar\\bar", "\\frac{}{}", {
|
||
"\\bar": " ",
|
||
});
|
||
});
|
||
*/
|
||
|
||
it("should build \\overset and \\underset", function() {
|
||
expect("\\overset{f}{\\rightarrow} Y").toBuild();
|
||
expect("\\underset{f}{\\rightarrow} Y").toBuild();
|
||
});
|
||
|
||
it("should build \\iff, \\implies, \\impliedby", function() {
|
||
expect("X \\iff Y").toBuild();
|
||
expect("X \\implies Y").toBuild();
|
||
expect("X \\impliedby Y").toBuild();
|
||
});
|
||
|
||
it("should allow aliasing characters", function() {
|
||
compareParseTree("x’=c", "x'=c", {
|
||
"’": "'",
|
||
});
|
||
});
|
||
|
||
it("\\@firstoftwo should consume both, and avoid errors", function() {
|
||
expect("\\@firstoftwo{yes}{no}").toParseLike("yes");
|
||
expect("\\@firstoftwo{yes}{1'_2^3}").toParseLike("yes");
|
||
});
|
||
|
||
it("\\@ifstar should consume star but nothing else", function() {
|
||
expect("\\@ifstar{yes}{no}*!").toParseLike("yes!");
|
||
expect("\\@ifstar{yes}{no}?!").toParseLike("no?!");
|
||
});
|
||
|
||
it("\\@ifnextchar should not consume anything", function() {
|
||
expect("\\@ifnextchar!{yes}{no}!!").toParseLike("yes!!");
|
||
expect("\\@ifnextchar!{yes}{no}?!").toParseLike("no?!");
|
||
});
|
||
|
||
it("\\@ifstar should consume star but nothing else", function() {
|
||
expect("\\@ifstar{yes}{no}*!").toParseLike("yes!");
|
||
expect("\\@ifstar{yes}{no}?!").toParseLike("no?!");
|
||
});
|
||
|
||
it("\\TextOrMath should work immediately", function() {
|
||
expect("\\TextOrMath{text}{math}").toParseLike("math");
|
||
});
|
||
|
||
it("\\TextOrMath should work after other math", function() {
|
||
expect("x+\\TextOrMath{text}{math}").toParseLike("x+math");
|
||
});
|
||
|
||
it("\\TextOrMath should work immediately after \\text", function() {
|
||
expect("\\text{\\TextOrMath{text}{math}}").toParseLike("\\text{text}");
|
||
});
|
||
|
||
it("\\TextOrMath should work later after \\text", function() {
|
||
expect("\\text{hello \\TextOrMath{text}{math}}")
|
||
.toParseLike("\\text{hello text}");
|
||
});
|
||
|
||
it("\\TextOrMath should work immediately after \\text ends", function() {
|
||
expect("\\text{\\TextOrMath{text}{math}}\\TextOrMath{text}{math}")
|
||
.toParseLike("\\text{text}math");
|
||
});
|
||
|
||
it("\\TextOrMath should work immediately after $", function() {
|
||
expect("\\text{$\\TextOrMath{text}{math}$}")
|
||
.toParseLike("\\text{$math$}");
|
||
});
|
||
|
||
it("\\TextOrMath should work later after $", function() {
|
||
expect("\\text{$x+\\TextOrMath{text}{math}$}")
|
||
.toParseLike("\\text{$x+math$}");
|
||
});
|
||
|
||
it("\\TextOrMath should work immediately after $ ends", function() {
|
||
expect("\\text{$\\TextOrMath{text}{math}$\\TextOrMath{text}{math}}")
|
||
.toParseLike("\\text{$math$text}");
|
||
});
|
||
|
||
it("\\TextOrMath should work in a macro", function() {
|
||
compareParseTree("\\mode\\text{\\mode$\\mode$\\mode}\\mode",
|
||
"math\\text{text$math$text}math",
|
||
{"\\mode": "\\TextOrMath{text}{math}"});
|
||
});
|
||
|
||
it("\\TextOrMath should work in a macro passed to \\text", function() {
|
||
compareParseTree("\\text\\mode", "\\text t",
|
||
{"\\mode": "\\TextOrMath{t}{m}"});
|
||
});
|
||
|
||
// TODO(edemaine): This doesn't work yet. Parses like `\text text`,
|
||
// which doesn't treat all four letters as an argument.
|
||
//it("\\TextOrMath should work in a macro passed to \\text", function() {
|
||
// compareParseTree("\\text\\mode", "\\text{text}",
|
||
// {"\\mode": "\\TextOrMath{text}{math}"});
|
||
//});
|
||
|
||
// This may change in the future, if we support the extra features of
|
||
// \hspace.
|
||
it("should treat \\hspace, \\hskip like \\kern", function() {
|
||
expect("\\hspace{1em}").toParseLike("\\kern1em");
|
||
expect("\\hskip{1em}").toParseLike("\\kern1em");
|
||
});
|
||
|
||
it("should expand \\limsup as expected", () => {
|
||
expect("\\limsup")
|
||
.toParseLike("\\mathop{\\operatorname{lim\\,sup}}\\limits");
|
||
});
|
||
|
||
it("should expand \\liminf as expected", () => {
|
||
expect("\\liminf")
|
||
.toParseLike("\\mathop{\\operatorname{lim\\,inf}}\\limits");
|
||
});
|
||
});
|
||
|
||
describe("A parser taking String objects", function() {
|
||
it("should not fail on an empty String object", function() {
|
||
expect(new String("")).toParse();
|
||
});
|
||
|
||
it("should parse the same as a regular string", function() {
|
||
expect(new String("xy")).toParseLike("xy");
|
||
expect(new String("\\div")).toParseLike("\\div");
|
||
expect(new String("\\frac 1 2")).toParseLike("\\frac 1 2");
|
||
});
|
||
});
|
||
|
||
describe("Unicode accents", function() {
|
||
it("should parse Latin-1 letters in math mode", function() {
|
||
// TODO(edemaine): Unsupported Latin-1 letters in math: ÅåÇÐÞçðþ
|
||
expect("ÀÁÂÃÄÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäèéêëìíîïñòóôõöùúûüýÿ")
|
||
.toParseLike(
|
||
"\\grave A\\acute A\\hat A\\tilde A\\ddot A" +
|
||
"\\grave E\\acute E\\hat E\\ddot E" +
|
||
"\\grave I\\acute I\\hat I\\ddot I" +
|
||
"\\tilde N" +
|
||
"\\grave O\\acute O\\hat O\\tilde O\\ddot O" +
|
||
"\\grave U\\acute U\\hat U\\ddot U" +
|
||
"\\acute Y" +
|
||
"\\grave a\\acute a\\hat a\\tilde a\\ddot a" +
|
||
"\\grave e\\acute e\\hat e\\ddot e" +
|
||
"\\grave ı\\acute ı\\hat ı\\ddot ı" +
|
||
"\\tilde n" +
|
||
"\\grave o\\acute o\\hat o\\tilde o\\ddot o" +
|
||
"\\grave u\\acute u\\hat u\\ddot u" +
|
||
"\\acute y\\ddot y",
|
||
{unicodeTextInMathMode: true});
|
||
});
|
||
|
||
it("should parse Latin-1 letters in text mode", function() {
|
||
// TODO(edemaine): Unsupported Latin-1 letters in text: ÇÐÞçðþ
|
||
expect("\\text{ÀÁÂÃÄÅÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåèéêëìíîïñòóôõöùúûüýÿ}")
|
||
.toParseLike(
|
||
"\\text{\\`A\\'A\\^A\\~A\\\"A\\r A" +
|
||
"\\`E\\'E\\^E\\\"E" +
|
||
"\\`I\\'I\\^I\\\"I" +
|
||
"\\~N" +
|
||
"\\`O\\'O\\^O\\~O\\\"O" +
|
||
"\\`U\\'U\\^U\\\"U" +
|
||
"\\'Y" +
|
||
"\\`a\\'a\\^a\\~a\\\"a\\r a" +
|
||
"\\`e\\'e\\^e\\\"e" +
|
||
"\\`ı\\'ı\\^ı\\\"ı" +
|
||
"\\~n" +
|
||
"\\`o\\'o\\^o\\~o\\\"o" +
|
||
"\\`u\\'u\\^u\\\"u" +
|
||
"\\'y\\\"y}");
|
||
});
|
||
|
||
it("should support \\aa in text mode", function() {
|
||
expect("\\text{\\aa\\AA}").toParseLike("\\text{\\r a\\r A}");
|
||
expect("\\aa").toNotParse();
|
||
expect("\\Aa").toNotParse();
|
||
});
|
||
|
||
it("should parse combining characters", function() {
|
||
expect("A\u0301C\u0301").toParseLike("Á\\acute C",
|
||
{unicodeTextInMathMode: true});
|
||
expect("\\text{A\u0301C\u0301}").toParseLike("\\text{Á\\'C}");
|
||
});
|
||
|
||
it("should parse multi-accented characters", function() {
|
||
expect("ấā́ắ\\text{ấā́ắ}").toParse({unicodeTextInMathMode: true});
|
||
// Doesn't parse quite the same as
|
||
// "\\text{\\'{\\^a}\\'{\\=a}\\'{\\u a}}" because of the ordgroups.
|
||
});
|
||
|
||
it("should parse accented i's and j's", function() {
|
||
expect("íȷ́").toParseLike("\\acute ı\\acute ȷ",
|
||
{unicodeTextInMathMode: true});
|
||
expect("ấā́ắ\\text{ấā́ắ}").toParse({unicodeTextInMathMode: true});
|
||
});
|
||
});
|
||
|
||
describe("Unicode", function() {
|
||
it("should parse negated relations", function() {
|
||
expect("∉∤∦≁≆≠≨≩≮≯≰≱⊀⊁⊈⊉⊊⊋⊬⊭⊮⊯⋠⋡⋦⋧⋨⋩⋬⋭⪇⪈⪉⪊⪵⪶⪹⪺⫋⫌").toParse();
|
||
});
|
||
|
||
it("should parse relations", function() {
|
||
expect("∈∋∝∼∽≂≃≅≈≊≍≎≏≐≑≒≓≖≗≜≡≤≥≦≧≪≫≬≳≷≺≻≼≽≾≿∴∵∣≔≕⩴⋘⋙").toParse();
|
||
});
|
||
|
||
it("should parse big operators", function() {
|
||
expect("∏∐∑∫∬∭∮⋀⋁⋂⋃⨀⨁⨂⨄⨆").toParse();
|
||
});
|
||
|
||
it("should parse more relations", function() {
|
||
expect("⊂⊃⊆⊇⊏⊐⊑⊒⊢⊣⊩⊪⊸⋈⋍⋐⋑⋔⋛⋞⋟⌢⌣⩾⪆⪌⪕⪖⪯⪰⪷⪸⫅⫆≘≙≚≛≝≞≟").toBuild();
|
||
});
|
||
|
||
it("should parse symbols", function() {
|
||
expect("£¥ðℂℍℑℓℕ℘ℙℚℜℝℤℲℵℶℷℸ⅁∀∁∂∃∇∞∠∡∢♠♡♢♣♭♮♯✓°\u00b7").toParse();
|
||
});
|
||
|
||
it("should build Greek capital letters", function() {
|
||
expect("\u0391\u0392\u0395\u0396\u0397\u0399\u039A\u039C\u039D" +
|
||
"\u039F\u03A1\u03A4\u03A7").toBuild();
|
||
});
|
||
|
||
it("should parse arrows", function() {
|
||
expect("←↑→↓↔↕↖↗↘↙↚↛↞↠↢↣↦↩↪↫↬↭↮↰↱↶↷↼↽↾↾↿⇀⇁⇂⇃⇄⇆⇇⇈⇉").toParse();
|
||
});
|
||
|
||
it("should parse more arrows", function() {
|
||
expect("⇊⇋⇌⇍⇎⇏⇐⇑⇒⇓⇔⇕⇚⇛⇝⟵⟶⟷⟸⟹⟺⟼").toParse();
|
||
});
|
||
|
||
it("should parse binary operators", function() {
|
||
expect("±×÷∓∔∧∨∩∪≀⊎⊓⊔⊕⊖⊗⊘⊙⊚⊛⊝⊞⊟⊠⊡⊺⊻⊼⋇⋉⋊⋋⋌⋎⋏⋒⋓⩞\u22C5").toParse();
|
||
});
|
||
|
||
it("should build delimiters", function() {
|
||
expect("\\left\u230A\\frac{a}{b}\\right\u230B").toBuild();
|
||
expect("\\left\u2308\\frac{a}{b}\\right\u2308").toBuild();
|
||
expect("\\left\u27ee\\frac{a}{b}\\right\u27ef").toBuild();
|
||
expect("\\left\u27e8\\frac{a}{b}\\right\u27e9").toBuild();
|
||
expect("\\left\u23b0\\frac{a}{b}\\right\u23b1").toBuild();
|
||
expect("┌x┐ └x┘").toBuild();
|
||
});
|
||
|
||
it("should build some surrogate pairs", function() {
|
||
let wideCharStr = "";
|
||
wideCharStr += String.fromCharCode(0xD835, 0xDC00); // bold A
|
||
wideCharStr += String.fromCharCode(0xD835, 0xDC68); // bold italic A
|
||
wideCharStr += String.fromCharCode(0xD835, 0xDD04); // Fraktur A
|
||
wideCharStr += String.fromCharCode(0xD835, 0xDD38); // double-struck
|
||
wideCharStr += String.fromCharCode(0xD835, 0xDC9C); // script A
|
||
wideCharStr += String.fromCharCode(0xD835, 0xDDA0); // sans serif A
|
||
wideCharStr += String.fromCharCode(0xD835, 0xDDD4); // bold sans A
|
||
wideCharStr += String.fromCharCode(0xD835, 0xDE08); // italic sans A
|
||
wideCharStr += String.fromCharCode(0xD835, 0xDE70); // monospace A
|
||
wideCharStr += String.fromCharCode(0xD835, 0xDFCE); // bold zero
|
||
wideCharStr += String.fromCharCode(0xD835, 0xDFE2); // sans serif zero
|
||
wideCharStr += String.fromCharCode(0xD835, 0xDFEC); // bold sans zero
|
||
wideCharStr += String.fromCharCode(0xD835, 0xDFF6); // monospace zero
|
||
expect(wideCharStr).toBuild();
|
||
|
||
let wideCharText = "\text{";
|
||
wideCharText += String.fromCharCode(0xD835, 0xDC00); // bold A
|
||
wideCharText += String.fromCharCode(0xD835, 0xDC68); // bold italic A
|
||
wideCharText += String.fromCharCode(0xD835, 0xDD04); // Fraktur A
|
||
wideCharText += String.fromCharCode(0xD835, 0xDD38); // double-struck
|
||
wideCharText += String.fromCharCode(0xD835, 0xDC9C); // script A
|
||
wideCharText += String.fromCharCode(0xD835, 0xDDA0); // sans serif A
|
||
wideCharText += String.fromCharCode(0xD835, 0xDDD4); // bold sans A
|
||
wideCharText += String.fromCharCode(0xD835, 0xDE08); // italic sans A
|
||
wideCharText += String.fromCharCode(0xD835, 0xDE70); // monospace A
|
||
wideCharText += String.fromCharCode(0xD835, 0xDFCE); // bold zero
|
||
wideCharText += String.fromCharCode(0xD835, 0xDFE2); // sans serif zero
|
||
wideCharText += String.fromCharCode(0xD835, 0xDFEC); // bold sans zero
|
||
wideCharText += String.fromCharCode(0xD835, 0xDFF6); // monospace zero
|
||
wideCharText += "}";
|
||
expect(wideCharText).toBuild();
|
||
});
|
||
});
|
||
|
||
describe("The maxSize setting", function() {
|
||
const rule = "\\rule{999em}{999em}";
|
||
|
||
it("should clamp size when set", function() {
|
||
const built = getBuilt(rule, new Settings({maxSize: 5}))[0];
|
||
expect(built.style.borderRightWidth).toEqual("5em");
|
||
expect(built.style.borderTopWidth).toEqual("5em");
|
||
});
|
||
|
||
it("should not clamp size when not set", function() {
|
||
const built = getBuilt(rule)[0];
|
||
expect(built.style.borderRightWidth).toEqual("999em");
|
||
expect(built.style.borderTopWidth).toEqual("999em");
|
||
});
|
||
|
||
it("should make zero-width rules if a negative maxSize is passed", function() {
|
||
const built = getBuilt(rule, new Settings({maxSize: -5}))[0];
|
||
expect(built.style.borderRightWidth).toEqual("0em");
|
||
expect(built.style.borderTopWidth).toEqual("0em");
|
||
});
|
||
});
|
||
|
||
describe("The \\mathchoice function", function() {
|
||
const cmd = "\\sum_{k = 0}^{\\infty} x^k";
|
||
|
||
it("should render as if there is nothing other in display math", function() {
|
||
const plain = getBuilt("\\displaystyle" + cmd)[0];
|
||
const built = getBuilt(`\\displaystyle\\mathchoice{${cmd}}{T}{S}{SS}`)[0];
|
||
expect(built).toEqual(plain);
|
||
});
|
||
|
||
it("should render as if there is nothing other in text", function() {
|
||
const plain = getBuilt(cmd)[0];
|
||
const built = getBuilt(`\\mathchoice{D}{${cmd}}{S}{SS}`)[0];
|
||
expect(built).toEqual(plain);
|
||
});
|
||
|
||
it("should render as if there is nothing other in scriptstyle", function() {
|
||
const plain = getBuilt(`x_{${cmd}}`)[0];
|
||
const built = getBuilt(`x_{\\mathchoice{D}{T}{${cmd}}{SS}}`)[0];
|
||
expect(built).toEqual(plain);
|
||
});
|
||
|
||
it("should render as if there is nothing other in scriptscriptstyle", function() {
|
||
const plain = getBuilt(`x_{y_{${cmd}}}`)[0];
|
||
const built = getBuilt(`x_{y_{\\mathchoice{D}{T}{S}{${cmd}}}}`)[0];
|
||
expect(built).toEqual(plain);
|
||
});
|
||
});
|
||
|
||
describe("Newlines via \\\\ and \\newline", function() {
|
||
it("should build \\\\ and \\newline the same", () => {
|
||
expect("hello \\\\ world").toBuildLike("hello \\newline world");
|
||
expect("hello \\\\[1ex] world").toBuildLike(
|
||
"hello \\newline[1ex] world");
|
||
});
|
||
|
||
it("should not allow \\cr at top level", () => {
|
||
expect("hello \\cr world").toNotParse();
|
||
});
|
||
});
|
||
|
||
describe("Symbols", function() {
|
||
it("should parse \\text{\\i\\j}", () => {
|
||
expect("\\text{\\i\\j}").toBuild();
|
||
});
|
||
|
||
it("should parse spacing functions in math or text mode", () => {
|
||
expect("A\\;B\\,C\\nobreakspace \\text{A\\;B\\,C\\nobreakspace}").toBuild();
|
||
});
|
||
|
||
it("should render ligature commands like their unicode characters", () => {
|
||
const commands = getBuilt("\\text{\\ae\\AE\\oe\\OE\\o\\O\\ss}");
|
||
const unicode = getBuilt("\\text{æÆœŒøØß}");
|
||
expect(commands).toEqual(unicode);
|
||
});
|
||
});
|
||
|
||
describe("unicodeTextInMathMode setting", function() {
|
||
it("should allow unicode text when true", () => {
|
||
expect("é").toParse({unicodeTextInMathMode: true});
|
||
expect("試").toParse({unicodeTextInMathMode: true});
|
||
});
|
||
|
||
it("should forbid unicode text when false", () => {
|
||
expect("é").toNotParse({unicodeTextInMathMode: false});
|
||
expect("試").toNotParse({unicodeTextInMathMode: false});
|
||
});
|
||
|
||
it("should forbid unicode text when default", () => {
|
||
expect("é").toNotParse();
|
||
expect("試").toNotParse();
|
||
});
|
||
|
||
it("should always allow unicode text in text mode", () => {
|
||
expect("\\text{é試}").toParse({unicodeTextInMathMode: false});
|
||
expect("\\text{é試}").toParse({unicodeTextInMathMode: true});
|
||
expect("\\text{é試}").toParse();
|
||
});
|
||
});
|
||
|
||
describe("Internal __* interface", function() {
|
||
const latex = "\\sum_{k = 0}^{\\infty} x^k";
|
||
const rendered = katex.renderToString(latex);
|
||
|
||
it("__parse renders same as renderToString", () => {
|
||
const parsed = katex.__parse(latex);
|
||
expect(buildTree(parsed, latex, new Settings()).toMarkup())
|
||
.toEqual(rendered);
|
||
});
|
||
|
||
it("__renderToDomTree renders same as renderToString", () => {
|
||
const tree = katex.__renderToDomTree(latex);
|
||
expect(tree.toMarkup()).toEqual(rendered);
|
||
});
|
||
|
||
it("__renderToHTMLTree renders same as renderToString sans MathML", () => {
|
||
const tree = katex.__renderToHTMLTree(latex);
|
||
const renderedSansMathML = rendered.replace(
|
||
/<span class="katex-mathml">.*?<\/span>/, '');
|
||
expect(tree.toMarkup()).toEqual(renderedSansMathML);
|
||
});
|
||
});
|