mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-09 04:58:40 +00:00
Summary: Implicit groups are objects that act like groups but don't have brackets around them. This is used for things like sizing functions or font-change functions that can occur in the middle of the group, but act like they apply to a group after them which stops when the current group stops. E.g. `Hello {world \Large hello} world` produces normal, normal, Large, normal text. (Note, I just came up with the name implicit group, I don't think this is actually how it is parsed in LaTeX but it fits nicely with the current parsing scheme and seems to work well). For now, apply this to the sizing functions (we don't have any other functions that act like this). Also note that this doesn't really do much practically because we limit sizing functions to only be on the top level of the expression, but it does let people do `\Large x` and get a large `x`, without having to add braces. Test Plan: - Run the tests, see they work - Make sure `abc \Large abc` looks correct Reviewers: alpert Reviewed By: alpert Differential Revision: http://phabricator.khanacademy.org/D10876
629 lines
16 KiB
JavaScript
629 lines
16 KiB
JavaScript
var buildTree = require("../buildTree");
|
|
var parseTree = require("../parseTree");
|
|
|
|
describe("A parser", function() {
|
|
it("should not fail on an empty string", function() {
|
|
expect(function() {
|
|
parseTree("");
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should ignore whitespace", function() {
|
|
var parseA = parseTree(" x y ");
|
|
var parseB = parseTree("xy");
|
|
expect(parseA).toEqual(parseB);
|
|
});
|
|
});
|
|
|
|
describe("An ord parser", function() {
|
|
var expression = "1234|/@.\"`abcdefgzABCDEFGZ";
|
|
|
|
it("should not fail", function() {
|
|
expect(function() {
|
|
parseTree(expression);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should build a list of ords", function() {
|
|
var parse = parseTree(expression);
|
|
|
|
expect(parse).toBeTruthy();
|
|
|
|
for (var i = 0; i < parse.length; i++) {
|
|
var group = parse[i];
|
|
expect(group.type).toMatch("ord");
|
|
}
|
|
});
|
|
|
|
it("should parse the right number of ords", function() {
|
|
var parse = parseTree(expression);
|
|
|
|
expect(parse.length).toBe(expression.length);
|
|
});
|
|
});
|
|
|
|
describe("A bin parser", function() {
|
|
var expression = "+-*\\cdot\\pm\\div";
|
|
|
|
it("should not fail", function() {
|
|
expect(function() {
|
|
parseTree(expression);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should build a list of bins", function() {
|
|
var parse = parseTree(expression);
|
|
expect(parse).toBeTruthy();
|
|
|
|
for (var i = 0; i < parse.length; i++) {
|
|
var group = parse[i];
|
|
expect(group.type).toMatch("bin");
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("A rel parser", function() {
|
|
var expression = "=<>\\leq\\geq\\neq\\nleq\\ngeq\\cong";
|
|
|
|
it("should not fail", function() {
|
|
expect(function() {
|
|
parseTree(expression);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should build a list of rels", function() {
|
|
var parse = parseTree(expression);
|
|
expect(parse).toBeTruthy();
|
|
|
|
for (var i = 0; i < parse.length; i++) {
|
|
var group = parse[i];
|
|
expect(group.type).toMatch("rel");
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("A punct parser", function() {
|
|
var expression = ",;\\colon";
|
|
|
|
it("should not fail", function() {
|
|
expect(function() {
|
|
parseTree(expression);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should build a list of puncts", function() {
|
|
var parse = parseTree(expression);
|
|
expect(parse).toBeTruthy();
|
|
|
|
for (var i = 0; i < parse.length; i++) {
|
|
var group = parse[i];
|
|
expect(group.type).toMatch("punct");
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("An open parser", function() {
|
|
var expression = "([";
|
|
|
|
it("should not fail", function() {
|
|
expect(function() {
|
|
parseTree(expression);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should build a list of opens", function() {
|
|
var parse = parseTree(expression);
|
|
expect(parse).toBeTruthy();
|
|
|
|
for (var i = 0; i < parse.length; i++) {
|
|
var group = parse[i];
|
|
expect(group.type).toMatch("open");
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("A close parser", function() {
|
|
var expression = ")]?!";
|
|
|
|
it("should not fail", function() {
|
|
expect(function() {
|
|
parseTree(expression);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should build a list of closes", function() {
|
|
var parse = parseTree(expression);
|
|
expect(parse).toBeTruthy();
|
|
|
|
for (var i = 0; i < parse.length; i++) {
|
|
var group = parse[i];
|
|
expect(group.type).toMatch("close");
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("A subscript and superscript parser", function() {
|
|
it("should not fail on superscripts", function() {
|
|
expect(function() {
|
|
parseTree("x^2");
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should not fail on subscripts", function() {
|
|
expect(function() {
|
|
parseTree("x_3");
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should not fail on both subscripts and superscripts", function() {
|
|
expect(function() {
|
|
parseTree("x^2_3");
|
|
}).not.toThrow();
|
|
|
|
expect(function() {
|
|
parseTree("x_2^3");
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should not fail when there is no nucleus", function() {
|
|
expect(function() {
|
|
parseTree("^3");
|
|
}).not.toThrow();
|
|
|
|
expect(function() {
|
|
parseTree("_2");
|
|
}).not.toThrow();
|
|
|
|
expect(function() {
|
|
parseTree("^3_2");
|
|
}).not.toThrow();
|
|
|
|
expect(function() {
|
|
parseTree("_2^3");
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should produce supsubs for superscript", function() {
|
|
var parse = parseTree("x^2")[0];
|
|
|
|
expect(parse.type).toBe("supsub");
|
|
expect(parse.value.base).toBeDefined();
|
|
expect(parse.value.sup).toBeDefined();
|
|
expect(parse.value.sub).toBeUndefined();
|
|
});
|
|
|
|
it("should produce supsubs for subscript", function() {
|
|
var parse = parseTree("x_3")[0];
|
|
|
|
expect(parse.type).toBe("supsub");
|
|
expect(parse.value.base).toBeDefined();
|
|
expect(parse.value.sub).toBeDefined();
|
|
expect(parse.value.sup).toBeUndefined();
|
|
});
|
|
|
|
it("should produce supsubs for ^_", function() {
|
|
var parse = parseTree("x^2_3")[0];
|
|
|
|
expect(parse.type).toBe("supsub");
|
|
expect(parse.value.base).toBeDefined();
|
|
expect(parse.value.sup).toBeDefined();
|
|
expect(parse.value.sub).toBeDefined();
|
|
});
|
|
|
|
it("should produce supsubs for _^", function() {
|
|
var parse = parseTree("x_3^2")[0];
|
|
|
|
expect(parse.type).toBe("supsub");
|
|
expect(parse.value.base).toBeDefined();
|
|
expect(parse.value.sup).toBeDefined();
|
|
expect(parse.value.sub).toBeDefined();
|
|
});
|
|
|
|
it("should produce the same thing regardless of order", function() {
|
|
var parseA = parseTree("x^2_3");
|
|
var parseB = parseTree("x_3^2");
|
|
|
|
expect(parseA).toEqual(parseB);
|
|
});
|
|
|
|
it("should not parse double subscripts or superscripts", function() {
|
|
expect(function() {
|
|
parseTree("x^x^x");
|
|
}).toThrow();
|
|
|
|
expect(function() {
|
|
parseTree("x_x_x");
|
|
}).toThrow();
|
|
|
|
expect(function() {
|
|
parseTree("x_x^x_x");
|
|
}).toThrow();
|
|
|
|
expect(function() {
|
|
parseTree("x_x^x^x");
|
|
}).toThrow();
|
|
|
|
expect(function() {
|
|
parseTree("x^x_x_x");
|
|
}).toThrow();
|
|
|
|
expect(function() {
|
|
parseTree("x^x_x^x");
|
|
}).toThrow();
|
|
});
|
|
|
|
it("should work correctly with {}s", function() {
|
|
expect(function() {
|
|
parseTree("x^{2+3}");
|
|
}).not.toThrow();
|
|
|
|
expect(function() {
|
|
parseTree("x_{3-2}");
|
|
}).not.toThrow();
|
|
|
|
expect(function() {
|
|
parseTree("x^{2+3}_3");
|
|
}).not.toThrow();
|
|
|
|
expect(function() {
|
|
parseTree("x^2_{3-2}");
|
|
}).not.toThrow();
|
|
|
|
expect(function() {
|
|
parseTree("x^{2+3}_{3-2}");
|
|
}).not.toThrow();
|
|
|
|
expect(function() {
|
|
parseTree("x_{3-2}^{2+3}");
|
|
}).not.toThrow();
|
|
|
|
expect(function() {
|
|
parseTree("x_3^{2+3}");
|
|
}).not.toThrow();
|
|
|
|
expect(function() {
|
|
parseTree("x_{3-2}^2");
|
|
}).not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe("A subscript and superscript tree-builder", function() {
|
|
it("should not fail when there is no nucleus", function() {
|
|
expect(function() {
|
|
buildTree(parseTree("^3"));
|
|
}).not.toThrow();
|
|
|
|
expect(function() {
|
|
buildTree(parseTree("_2"));
|
|
}).not.toThrow();
|
|
|
|
expect(function() {
|
|
buildTree(parseTree("^3_2"));
|
|
}).not.toThrow();
|
|
|
|
expect(function() {
|
|
buildTree(parseTree("_2^3"));
|
|
}).not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe("A group parser", function() {
|
|
it("should not fail", function() {
|
|
expect(function() {
|
|
parseTree("{xy}");
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should produce a single ord", function() {
|
|
var parse = parseTree("{xy}");
|
|
|
|
expect(parse.length).toBe(1);
|
|
|
|
var ord = parse[0];
|
|
|
|
expect(ord.type).toMatch("ord");
|
|
expect(ord.value).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe("An implicit group parser", function() {
|
|
it("should not fail", function() {
|
|
expect(function() {
|
|
parseTree("\\Large x");
|
|
parseTree("abc {abc \Large xyz} abc");
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should produce a single object", function() {
|
|
var parse = parseTree("\\Large abc");
|
|
|
|
expect(parse.length).toBe(1);
|
|
|
|
var sizing = parse[0];
|
|
|
|
expect(sizing.type).toMatch("sizing");
|
|
expect(sizing.value).toBeTruthy();
|
|
});
|
|
|
|
it("should apply only after the function", function() {
|
|
var parse = parseTree("a \\Large abc");
|
|
|
|
expect(parse.length).toBe(2);
|
|
|
|
var sizing = parse[1];
|
|
|
|
expect(sizing.type).toMatch("sizing");
|
|
expect(sizing.value.value.value.length).toBe(3);
|
|
});
|
|
|
|
it("should stop at the ends of groups", function() {
|
|
var parse = parseTree("a { b \\Large c } d");
|
|
|
|
var group = parse[1];
|
|
var sizing = group.value[1];
|
|
|
|
expect(sizing.type).toMatch("sizing");
|
|
expect(sizing.value.value.value.length).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe("A function parser", function() {
|
|
it("should parse no argument functions", function() {
|
|
expect(function() {
|
|
parseTree("\\div");
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should parse 1 argument functions", function() {
|
|
expect(function() {
|
|
parseTree("\\blue x");
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should parse 2 argument functions", function() {
|
|
expect(function() {
|
|
parseTree("\\frac 1 2");
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should not parse 1 argument functions with no arguments", function() {
|
|
expect(function() {
|
|
parseTree("\\blue");
|
|
}).toThrow();
|
|
});
|
|
|
|
it("should not parse 2 argument functions with 0 or 1 arguments", function() {
|
|
expect(function() {
|
|
parseTree("\\frac");
|
|
}).toThrow();
|
|
|
|
expect(function() {
|
|
parseTree("\\frac 1");
|
|
}).toThrow();
|
|
});
|
|
|
|
it("should not parse a function with text right after it", function() {
|
|
expect(function() {
|
|
parseTree("\\redx");
|
|
}).toThrow();
|
|
});
|
|
|
|
it("should parse a function with a number right after it", function() {
|
|
expect(function() {
|
|
parseTree("\\frac12");
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should parse some functions with text right after it", function() {
|
|
expect(function() {
|
|
parseTree("\\;x");
|
|
}).not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe("A frac parser", function() {
|
|
var expression = "\\frac{x}{y}";
|
|
var dfracExpression = "\\dfrac{x}{y}";
|
|
var tfracExpression = "\\tfrac{x}{y}";
|
|
|
|
it("should not fail", function() {
|
|
expect(function() {
|
|
parseTree(expression);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should produce a frac", function() {
|
|
var parse = parseTree(expression)[0];
|
|
|
|
expect(parse.type).toMatch("frac");
|
|
expect(parse.value.numer).toBeDefined();
|
|
expect(parse.value.denom).toBeDefined();
|
|
});
|
|
|
|
it("should also parse dfrac and tfrac", function() {
|
|
expect(function() {
|
|
parseTree(dfracExpression);
|
|
}).not.toThrow();
|
|
|
|
expect(function() {
|
|
parseTree(tfracExpression);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should parse dfrac and tfrac as fracs", function() {
|
|
var dfracParse = parseTree(dfracExpression)[0];
|
|
|
|
expect(dfracParse.type).toMatch("frac");
|
|
expect(dfracParse.value.numer).toBeDefined();
|
|
expect(dfracParse.value.denom).toBeDefined();
|
|
|
|
var tfracParse = parseTree(tfracExpression)[0];
|
|
|
|
expect(tfracParse.type).toMatch("frac");
|
|
expect(tfracParse.value.numer).toBeDefined();
|
|
expect(tfracParse.value.denom).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe("A sizing parser", function() {
|
|
var sizeExpression = "\\Huge{x}\\small{x}";
|
|
var nestedSizeExpression = "\\Huge{\\small{x}}";
|
|
|
|
it("should not fail", function() {
|
|
expect(function() {
|
|
parseTree(sizeExpression);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should produce a sizing node", function() {
|
|
var parse = parseTree(sizeExpression)[0];
|
|
|
|
expect(parse.type).toMatch("sizing");
|
|
expect(parse.value).toBeDefined();
|
|
});
|
|
|
|
it("should not parse a nested size expression", function() {
|
|
expect(function() {
|
|
parseExpression(nestedSizeExpression);
|
|
}).toThrow();
|
|
});
|
|
});
|
|
|
|
describe("A text parser", function() {
|
|
var textExpression = "\\text{a b}";
|
|
var badTextExpression = "\\text{a b%}";
|
|
var badTextExpression2 = "\\text x";
|
|
var nestedTextExpression = "\\text{a {b} \\blue{c}}";
|
|
var spaceTextExpression = "\\text{ a \\ }";
|
|
var leadingSpaceTextExpression = "\\text {moo}";
|
|
|
|
it("should not fail", function() {
|
|
expect(function() {
|
|
parseTree(textExpression);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should produce a text", function() {
|
|
var parse = parseTree(textExpression)[0];
|
|
|
|
expect(parse.type).toMatch("text");
|
|
expect(parse.value).toBeDefined();
|
|
});
|
|
|
|
it("should produce textords instead of mathords", function() {
|
|
var parse = parseTree(textExpression)[0];
|
|
var group = parse.value.value;
|
|
|
|
expect(group[0].type).toMatch("textord");
|
|
});
|
|
|
|
it("should not parse bad text", function() {
|
|
expect(function() {
|
|
parseTree(badTextExpression);
|
|
}).toThrow();
|
|
expect(function() {
|
|
parseTree(badTextExpression2);
|
|
}).toThrow();
|
|
});
|
|
|
|
it("should parse nested expressions", function() {
|
|
expect(function() {
|
|
parseTree(nestedTextExpression);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should contract spaces", function() {
|
|
var parse = parseTree(spaceTextExpression)[0];
|
|
var group = parse.value.value;
|
|
|
|
expect(group[0].type).toMatch("spacing");
|
|
expect(group[1].type).toMatch("textord");
|
|
expect(group[2].type).toMatch("spacing");
|
|
expect(group[3].type).toMatch("spacing");
|
|
});
|
|
|
|
it("should ignore a space before the text group", function() {
|
|
var parse = parseTree(leadingSpaceTextExpression)[0];
|
|
// [m, o, o]
|
|
expect(parse.value.value.length).toBe(3);
|
|
expect(
|
|
parse.value.value.map(function(n) { return n.value; }).join("")
|
|
).toBe("moo");
|
|
});
|
|
});
|
|
|
|
describe("A color parser", function() {
|
|
var colorExpression = "\\blue{x}";
|
|
var customColorExpression = "\\color{#fA6}{x}";
|
|
var badCustomColorExpression = "\\color{bad-color}{x}";
|
|
|
|
it("should not fail", function() {
|
|
expect(function() {
|
|
parseTree(colorExpression);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should build a color node", function() {
|
|
var parse = parseTree(colorExpression)[0];
|
|
|
|
expect(parse.type).toMatch("color");
|
|
expect(parse.value.color).toBeDefined();
|
|
expect(parse.value.value).toBeDefined();
|
|
});
|
|
|
|
it("should parse a custom color", function() {
|
|
expect(function() {
|
|
parseTree(customColorExpression);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should correctly extract the custom color", function() {
|
|
var parse = parseTree(customColorExpression)[0];
|
|
|
|
expect(parse.value.color).toMatch("#fA6");
|
|
});
|
|
|
|
it("should not parse a bad custom color", function() {
|
|
expect(function() {
|
|
parseTree(badCustomColorExpression);
|
|
}).toThrow();
|
|
});
|
|
});
|
|
|
|
describe("A tie parser", function() {
|
|
var mathTie = "a~b";
|
|
var textTie = "\\text{a~ b}";
|
|
|
|
it("should parse ties in math mode", function() {
|
|
expect(function() {
|
|
parseTree(mathTie);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should parse ties in text mode", function() {
|
|
expect(function() {
|
|
parseTree(textTie);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should produce spacing in math mode", function() {
|
|
var parse = parseTree(mathTie);
|
|
|
|
expect(parse[1].type).toMatch("spacing");
|
|
});
|
|
|
|
it("should produce spacing in text mode", function() {
|
|
var text = parseTree(textTie)[0];
|
|
var parse = text.value.value;
|
|
|
|
expect(parse[1].type).toMatch("spacing");
|
|
});
|
|
|
|
it("should not contract with spaces in text mode", function() {
|
|
var text = parseTree(textTie)[0];
|
|
var parse = text.value.value;
|
|
|
|
expect(parse[2].type).toMatch("spacing");
|
|
});
|
|
});
|