commit 5f275aa9c154c0bb9623cd895933374897637554
parent 4617f191e37e3737313734620262155285c5f7d0
Author: Kevin Barabash <kevinb7@gmail.com>
Date: Sat, 21 Nov 2015 08:54:50 -0800
Merge pull request #385 from gagern/testErrors
Added unit tests for error messages
Diffstat:
| A | test/errors-spec.js | | | 345 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 345 insertions(+), 0 deletions(-)
diff --git a/test/errors-spec.js b/test/errors-spec.js
@@ -0,0 +1,345 @@
+/* global beforeEach: false */
+/* global jasmine: false */
+/* global expect: false */
+/* global it: false */
+/* global describe: false */
+
+var parseTree = require("../src/parseTree");
+var Settings = require("../src/Settings");
+
+var defaultSettings = new Settings({});
+
+beforeEach(function() {
+ jasmine.addMatchers({
+ toFailWithParseError: function(util, customEqualityTesters) {
+ var prefix = "KaTeX parse error: ";
+ return {
+ compare: function(actual, expected) {
+ try {
+ parseTree(actual, defaultSettings);
+ return {
+ pass: false,
+ message: "'" + actual + "' parsed without error"
+ };
+ } catch (e) {
+ if (expected === undefined) {
+ return {
+ pass: true,
+ message: "'" + actual + "' parsed with error"
+ };
+ }
+ var msg = e.message;
+ var exp = prefix + expected;
+ if (msg === exp) {
+ return {
+ pass: true,
+ message: "'" + actual + "'" +
+ " parsed with error '" + expected + "'"
+ };
+ } else if (msg.slice(0, 19) === prefix) {
+ return {
+ pass: false,
+ message: "'" + actual + "'" +
+ " parsed with error '" + msg.slice(19) +
+ "' but expected '" + expected + "'"
+ };
+ } else {
+ return {
+ pass: false,
+ message: "'" + actual + "'" +
+ " caused error '" + msg +
+ "' but expected '" + exp + "'"
+ };
+ }
+ }
+ }
+ };
+ }
+ });
+});
+
+describe("Parser:", function() {
+
+ describe("#handleInfixNodes", function() {
+ // TODO: The position information here is broken, should be fixed.
+ it("rejects repeated infix operators", function() {
+ expect("1\\over 2\\over 3").toFailWithParseError(
+ "only one infix operator per group at position -1: " +
+ "1\\over 2\\over ");
+ });
+ it("rejects conflicting infix operators", function() {
+ expect("1\\over 2\\choose 3").toFailWithParseError(
+ "only one infix operator per group at position -1: " +
+ "1\\over 2\\choos");
+ });
+ });
+
+ describe("#handleSupSubscript", function() {
+ it("rejects ^ at end of group", function() {
+ expect("{1^}").toFailWithParseError(
+ "Expected group after '^' at position 3: {1^̲}");
+ });
+ it("rejects _ at end of input", function() {
+ expect("1_").toFailWithParseError(
+ "Expected group after '_' at position 2: 1_̲");
+ });
+ it("rejects \\sqrt as argument to ^", function() {
+ expect("1^\\sqrt{2}").toFailWithParseError(
+ "Got function '\\sqrt' with no arguments as superscript" +
+ " at position 2: 1^̲\\sqrt{2}");
+ });
+ });
+
+ describe("#parseAtom", function() {
+ // TODO: The positions in the following error messages appear to be
+ // off by one, i.e. they should be one character later.
+ it("rejects \\limits without operator", function() {
+ expect("\\alpha\\limits\\omega").toFailWithParseError(
+ "Limit controls must follow a math operator" +
+ " at position 6: \\alpha̲\\limits\\omega");
+ });
+ it("rejects \\limits at the beginning of the input", function() {
+ expect("\\limits\\omega").toFailWithParseError(
+ "Limit controls must follow a math operator" +
+ " at position 0: ̲\\limits\\omega");
+ });
+ it("rejects double superscripts", function() {
+ expect("1^2^3").toFailWithParseError(
+ "Double superscript at position 3: 1^2̲^3");
+ expect("1^{2+3}_4^5").toFailWithParseError(
+ "Double superscript at position 9: 1^{2+3}_4̲^5");
+ });
+ it("rejects double subscripts", function() {
+ expect("1_2_3").toFailWithParseError(
+ "Double subscript at position 3: 1_2̲_3");
+ expect("1_{2+3}^4_5").toFailWithParseError(
+ "Double subscript at position 9: 1_{2+3}^4̲_5");
+ });
+ });
+
+ describe("#parseImplicitGroup", function() {
+ it("reports unknown environments", function() {
+ expect("\\begin{foo}bar\\end{foo}").toFailWithParseError(
+ "No such environment: foo at position 11:" +
+ " \\begin{foo}̲bar\\end{foo}");
+ });
+ it("reports mismatched environments", function() {
+ expect("\\begin{pmatrix}1&2\\\\3&4\\end{bmatrix}+5")
+ .toFailWithParseError(
+ "Mismatch: \\begin{pmatrix} matched by \\end{bmatrix}");
+ });
+ });
+
+ describe("#parseFunction", function() {
+ it("rejects math-mode functions in text mode", function() {
+ // TODO: The position info is missing here
+ expect("\\text{\\sqrt2 is irrational}").toFailWithParseError(
+ "Can't use function '\\sqrt' in text mode");
+ });
+ });
+
+ describe("#parseArguments", function() {
+ it("complains about missing argument at end of input", function() {
+ expect("2\\sqrt").toFailWithParseError(
+ "Expected group after '\\sqrt' at position 6: 2\\sqrt̲");
+ });
+ it("complains about missing argument at end of group", function() {
+ expect("1^{2\\sqrt}").toFailWithParseError(
+ "Expected group after '\\sqrt' at position 9: 1^{2\\sqrt̲}");
+ });
+ it("complains about functions as arguments to others", function() {
+ // TODO: The position looks pretty wrong here
+ expect("\\sqrt\\over2").toFailWithParseError(
+ "Got function '\\over' as argument to '\\sqrt'" +
+ " at position 9: \\sqrt\\ove̲r2");
+ });
+ });
+
+ describe("#parseArguments", function() {
+ it("complains about missing argument at end of input", function() {
+ expect("2\\sqrt").toFailWithParseError(
+ "Expected group after '\\sqrt' at position 6: 2\\sqrt̲");
+ });
+ it("complains about missing argument at end of group", function() {
+ expect("1^{2\\sqrt}").toFailWithParseError(
+ "Expected group after '\\sqrt' at position 9: 1^{2\\sqrt̲}");
+ });
+ it("complains about functions as arguments to others", function() {
+ // TODO: The position looks pretty wrong here
+ expect("\\sqrt\\over2").toFailWithParseError(
+ "Got function '\\over' as argument to '\\sqrt'" +
+ " at position 9: \\sqrt\\ove̲r2");
+ });
+ });
+
+});
+
+describe("Parser.expect calls:", function() {
+
+ describe("#parseInput expecting EOF", function() {
+ it("complains about extra }", function() {
+ expect("{1+2}}").toFailWithParseError(
+ "Expected 'EOF', got '}' at position 6: {1+2}}̲");
+ });
+ it("complains about extra \\end", function() {
+ expect("x\\end{matrix}").toFailWithParseError(
+ "Expected 'EOF', got '\\end' at position 5:" +
+ " x\\end̲{matrix}");
+ });
+ it("complains about top-level \\\\", function() {
+ expect("1\\\\2").toFailWithParseError(
+ "Expected 'EOF', got '\\\\' at position 3: 1\\\\̲2");
+ });
+ it("complains about top-level &", function() {
+ expect("1&2").toFailWithParseError(
+ "Expected 'EOF', got '&' at position 2: 1&̲2");
+ });
+ });
+
+ describe("#parseImplicitGroup expecting \\right", function() {
+ it("rejects missing \\right", function() {
+ expect("\\left(1+2)").toFailWithParseError(
+ "Expected '\\right', got 'EOF' at position 10:" +
+ " \\left(1+2)̲");
+ });
+ it("rejects incorrectly scoped \\right", function() {
+ expect("{\\left(1+2}\\right)").toFailWithParseError(
+ "Expected '\\right', got '}' at position 11:" +
+ " {\\left(1+2}̲\\right)");
+ });
+ });
+
+ // Can't test the expectation for \end after an environment
+ // since all existing arrays use parseArray which has its own expectation.
+
+ describe("#parseSpecialGroup expecting braces", function() {
+ it("complains about missing { for color", function() {
+ expect("\\color#ffffff{text}").toFailWithParseError(
+ "Expected '{', got '#' at position 7:" +
+ " \\color#̲ffffff{text}");
+ });
+ it("complains about missing { for size", function() {
+ expect("\\rule{1em}[2em]").toFailWithParseError(
+ "Expected '{', got '[' at position 11: \\rule{1em}[̲2em]");
+ });
+ // Can't test for the [ of an optional group since it's optional
+ it("complains about missing } for color", function() {
+ expect("\\color{#ffffff {text}").toFailWithParseError(
+ "Expected '}', got '{' at position 16:" +
+ " color{#ffffff {̲text}");
+ });
+ it("complains about missing ] for size", function() {
+ expect("\\rule[1em{2em}{3em}").toFailWithParseError(
+ "Expected ']', got '{' at position 10:" +
+ " \\rule[1em{̲2em}{3em}");
+ });
+ });
+
+ describe("#parseGroup expecting }", function() {
+ it("at end of file", function() {
+ expect("\\sqrt{2").toFailWithParseError(
+ "Expected '}', got 'EOF' at position 7: \\sqrt{2̲");
+ });
+ });
+
+ describe("#parseOptionalGroup expecting ]", function() {
+ it("at end of file", function() {
+ expect("\\sqrt[3").toFailWithParseError(
+ "Expected ']', got 'EOF' at position 7: \\sqrt[3̲");
+ });
+ it("before group", function() {
+ expect("\\sqrt[3{2}").toFailWithParseError(
+ "Expected ']', got 'EOF' at position 10: \\sqrt[3{2}̲");
+ });
+ });
+
+});
+
+describe("environments.js:", function() {
+
+ describe("parseArray", function() {
+ it("rejects missing \\end", function() {
+ expect("\\begin{matrix}1").toFailWithParseError(
+ "Expected & or \\\\ or \\end at position 15:" +
+ " \\begin{matrix}1̲");
+ });
+ it("rejects incorrectly scoped \\end", function() {
+ expect("{\\begin{matrix}1}\\end{matrix}").toFailWithParseError(
+ "Expected & or \\\\\ or \\end at position 17:" +
+ " begin{matrix}1}̲\\end{matrix}");
+ });
+ });
+
+ describe("array environment", function() {
+ it("rejects unknown column types", function() {
+ // TODO: The error position here looks strange
+ expect("\\begin{array}{cba}\\end{array}").toFailWithParseError(
+ "Unknown column alignment: b at position 18:" +
+ " gin{array}{cba}̲\\end{array}");
+ });
+ });
+
+});
+
+describe("functions.js:", function() {
+
+ describe("delimiter functions", function() {
+ it("reject invalid opening delimiters", function() {
+ expect("\\bigl 1 + 2 \\bigr").toFailWithParseError(
+ "Invalid delimiter: '1' after '\\bigl' at position 7:" +
+ " \\bigl 1̲ + 2 \\bigr");
+ });
+ it("reject invalid closing delimiters", function() {
+ expect("\\bigl(1+2\\bigr=3").toFailWithParseError(
+ "Invalid delimiter: '=' after '\\bigr' at position 15:" +
+ " \\bigl(1+2\\bigr=̲3");
+ });
+ });
+
+ describe("\\begin and \\end", function() {
+ it("reject invalid environment names", function() {
+ expect("\\begin{foobar}\\end{foobar}").toFailWithParseError(
+ "No such environment: foobar at position 14:" +
+ " \\begin{foobar}̲\\end{foobar}");
+ });
+ });
+
+});
+
+describe("Lexer:", function() {
+
+ describe("#_innerLex", function() {
+ it("rejects lone surrogate char", function() {
+ expect("\udcba").toFailWithParseError(
+ "Unexpected character: '\udcba' at position 0:" +
+ " \u0332\udcba");
+ });
+ it("rejects lone backslash at end of input", function() {
+ expect("\\").toFailWithParseError(
+ "Unexpected character: '\\' at position 0: ̲\\");
+ });
+ });
+
+ describe("#_innerLexColor", function() {
+ it("reject hex notation without #", function() {
+ expect("\\color{1a2b3c}{foo}").toFailWithParseError(
+ "Invalid color at position 7: \\color{̲1a2b3c}{foo}");
+ });
+ });
+
+ describe("#_innerLexSize", function() {
+ it("reject size without unit", function() {
+ expect("\\rule{0}{2em}").toFailWithParseError(
+ "Invalid size at position 6: \\rule{̲0}{2em}");
+ });
+ it("reject size with bogus unit", function() {
+ expect("\\rule{1au}{2em}").toFailWithParseError(
+ "Invalid unit: 'au' at position 6: \\rule{̲1au}{2em}");
+ });
+ it("reject size without number", function() {
+ expect("\\rule{em}{2em}").toFailWithParseError(
+ "Invalid size at position 6: \\rule{̲em}{2em}");
+ });
+ });
+
+});