scribble-math/test/katex-tests.js
Emily Eisenberg 0a3a2271f4 Add ability to calculate heights/depths
Summary:
This diff does a couple different things:

 - There is now a metrics/ folder, which contains the property files describing
   the metrics if the fonts, as well as a script for reading and printing the
   metrics in javascript.
 - Fractions and superscripts/subscripts are now rendered in slightly different
   ways now (notably, no use of inline-table). This allows for much more precise
   positioning of the superscripts, subscripts, numerators, and denominators,
   while still having an appropriate baseline. Also, there is no longer a
   sup/sub/supsub distinction, there are only supsubs with null sup/sub.
 - Using the new font metrics and by implementing the formulas found in The TeX
   Book, Appendix G, the heights and depths of all of the sub-expressions in a
   formula are now calculated. These are currently used to:
    - Correctly position superscripts, subscripts, numerators, and denominators
    - Adjust the height and depth of the overall expression so it takes up the
      appropriate space
 - Because we have to add attributes (height and depth) to every attribute, I
   have changed the way DOM nodes are assembled. Now, instead of assembling the
   DOM elements inline (which is a problem because we need to track
   height/depth, and we shouldn't (and can't in IE 8) attach raw attributes to DOM
   nodes), we assemble a pseudo-DOM structure with the extra information, and
   then actually assemble it at the very end.

The main page also now has an updated expression to show off and test the new
and improved parsing.

Test Plan:
View the main page, make sure that the expression renders. Make sure
that the tests pass. Make sure that expressions have the correct calculated
height (this is most easily tested by viewing them on the main page and making
sure that the top of the expression lines up with the bottom of the input box).

Reviewers: alpert

Reviewed By: alpert

Differential Revision: http://phabricator.khanacademy.org/D3442
2013-08-13 18:16:43 -07:00

405 lines
10 KiB
JavaScript

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";
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 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("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();
});
});