Refactor test helpers (#1336)

* Refactor test helpers

* Combine common codes from `parse` and `build`, and dispatch using
`Mode`
* Remove `toNotBuild` and `toNotParse` and use `expect.not`
* Remove `Warning` and instead mock `console.warn`
* Add `expect.toHavePassed` and check whether parse/build succeeded
lazily
* Improve failed test `message`:
  - Use color
  - Print stack traces(excluding internals)
  - Print diff
  - Follow jest matcher output style

* Update helpers.js

* Remove toBeTruthy checks

getParsed throws an error if parsing fails.

* Used tagged literals

* Use .toHaveLength

* Use tagged literals

* Use to{Build,Parse}Like where possible

* Remove compareParseTree

* Use snapshot where possible

* Join into one line where <88 chars

* Revert console.warn() to throw an error

Merge `expectToWarn` to `expectKaTeX`, like
`expect.toFailWithParseError`

* Remove call to console.warn from stack traces

* Fix merge errors

* Remove `getTree`

* Move `_getBuilt` into `getBuilt`
* Move default settings and tagging literal support in to `getParsed` 
and `getBuilt`
* Remove stack traces clean-up
* Remove `toHavePassed` matcher

* Extract `expected` string construction into `printExpectedResult`
This commit is contained in:
ylemkimon
2018-07-22 11:06:07 +09:00
committed by Kevin Barabash
parent c9947220b6
commit 71035c7111
7 changed files with 1054 additions and 1200 deletions

View File

@@ -1,20 +0,0 @@
// @flow
class Warning {
name: string;
message: string;
stack: string;
constructor(message: string) {
// $FlowFixMe
this.name = "Warning";
// $FlowFixMe
this.message = "Warning: " + message;
// $FlowFixMe
this.stack = new Error().stack;
}
}
// $FlowFixMe
Warning.prototype = Object.create(Error.prototype);
module.exports = Warning;

View File

@@ -585,6 +585,28 @@ exports[`A font parser \\boldsymbol should inherit mbin/mrel from argument 1`] =
]
`;
exports[`A parse tree generator generates a tree 1`] = `
[
{
"type": "supsub",
"mode": "math",
"value": {
"type": "supsub",
"base": {
"type": "mathord",
"mode": "math",
"value": "\\\\sigma"
},
"sup": {
"type": "textord",
"mode": "math",
"value": "2"
}
}
}
]
`;
exports[`A parser that does not throw on unsupported commands should build katex-error span for other type of KaTeX error 1`] = `
{
"attributes": {

View File

@@ -6,12 +6,12 @@ describe("Parser:", function() {
describe("#handleInfixNodes", function() {
it("rejects repeated infix operators", function() {
expect("1\\over 2\\over 3").toFailWithParseError(
expect`1\over 2\over 3`.toFailWithParseError(
"only one infix operator per group at position 9: " +
"1\\over 2\\̲o̲v̲e̲r̲ ̲3");
});
it("rejects conflicting infix operators", function() {
expect("1\\over 2\\choose 3").toFailWithParseError(
expect`1\over 2\choose 3`.toFailWithParseError(
"only one infix operator per group at position 9: " +
"1\\over 2\\̲c̲h̲o̲o̲s̲e̲ ̲3");
});
@@ -19,15 +19,15 @@ describe("Parser:", function() {
describe("#handleSupSubscript", function() {
it("rejects ^ at end of group", function() {
expect("{1^}").toFailWithParseError(
expect`{1^}`.toFailWithParseError(
"Expected group after '^' at position 3: {1^̲}");
});
it("rejects _ at end of input", function() {
expect("1_").toFailWithParseError(
expect`1_`.toFailWithParseError(
"Expected group after '_' at position 2: 1_̲");
});
it("rejects \\sqrt as argument to ^", function() {
expect("1^\\sqrt{2}").toFailWithParseError(
expect`1^\sqrt{2}`.toFailWithParseError(
"Got function '\\sqrt' with no arguments as superscript" +
" at position 2: 1^̲\\sqrt{2}");
});
@@ -35,47 +35,47 @@ describe("Parser:", function() {
describe("#parseAtom", function() {
it("rejects \\limits without operator", function() {
expect("\\alpha\\limits\\omega").toFailWithParseError(
expect`\alpha\limits\omega`.toFailWithParseError(
"Limit controls must follow a math operator" +
" at position 7: \\alpha\\̲l̲i̲m̲i̲t̲s̲\\omega");
});
it("rejects \\limits at the beginning of the input", function() {
expect("\\limits\\omega").toFailWithParseError(
expect`\limits\omega`.toFailWithParseError(
"Limit controls must follow a math operator" +
" at position 1: \\̲l̲i̲m̲i̲t̲s̲\\omega");
});
it("rejects double superscripts", function() {
expect("1^2^3").toFailWithParseError(
expect`1^2^3`.toFailWithParseError(
"Double superscript at position 4: 1^2^̲3");
expect("1^{2+3}_4^5").toFailWithParseError(
expect`1^{2+3}_4^5`.toFailWithParseError(
"Double superscript at position 10: 1^{2+3}_4^̲5");
});
it("rejects double superscripts involving primes", function() {
expect("1'_2^3").toFailWithParseError(
expect`1'_2^3`.toFailWithParseError(
"Double superscript at position 5: 1'_2^̲3");
expect("1^2'").toFailWithParseError(
expect`1^2'`.toFailWithParseError(
"Double superscript at position 4: 1^2'̲");
expect("1^2_3'").toFailWithParseError(
expect`1^2_3'`.toFailWithParseError(
"Double superscript at position 6: 1^2_3'̲");
expect("1'_2'").toFailWithParseError(
expect`1'_2'`.toFailWithParseError(
"Double superscript at position 5: 1'_2'̲");
});
it("rejects double subscripts", function() {
expect("1_2_3").toFailWithParseError(
expect`1_2_3`.toFailWithParseError(
"Double subscript at position 4: 1_2_̲3");
expect("1_{2+3}^4_5").toFailWithParseError(
expect`1_{2+3}^4_5`.toFailWithParseError(
"Double subscript at position 10: 1_{2+3}^4_̲5");
});
});
describe("#parseImplicitGroup", function() {
it("reports unknown environments", function() {
expect("\\begin{foo}bar\\end{foo}").toFailWithParseError(
expect`\begin{foo}bar\end{foo}`.toFailWithParseError(
"No such environment: foo at position 7:" +
" \\begin{̲f̲o̲o̲}̲bar\\end{foo}");
});
it("reports mismatched environments", function() {
expect("\\begin{pmatrix}1&2\\\\3&4\\end{bmatrix}+5")
expect`\begin{pmatrix}1&2\\3&4\end{bmatrix}+5`
.toFailWithParseError(
"Mismatch: \\begin{pmatrix} matched by \\end{bmatrix}" +
" at position 24: …matrix}1&2\\\\3&4\\̲e̲n̲d̲{bmatrix}+5");
@@ -84,12 +84,12 @@ describe("Parser:", function() {
describe("#parseFunction", function() {
it("rejects math-mode functions in text mode", function() {
expect("\\text{\\sqrt2 is irrational}").toFailWithParseError(
expect`\text{\sqrt2 is irrational}`.toFailWithParseError(
"Can't use function '\\sqrt' in text mode" +
" at position 7: \\text{\\̲s̲q̲r̲t̲2 is irrational…");
});
it("rejects text-mode-only functions in math mode", function() {
expect("\\'echec").toFailWithParseError(
expect`\'echec`.toFailWithParseError(
"Can't use function '\\'' in math mode" +
" at position 1: \\̲'̲echec");
});
@@ -97,17 +97,17 @@ describe("Parser:", function() {
describe("#parseArguments", function() {
it("complains about missing argument at end of input", function() {
expect("2\\sqrt").toFailWithParseError(
expect`2\sqrt`.toFailWithParseError(
"Expected group after '\\sqrt' at end of input: 2\\sqrt");
});
it("complains about missing argument at end of group", function() {
expect("1^{2\\sqrt}").toFailWithParseError(
expect`1^{2\sqrt}`.toFailWithParseError(
"Expected group after '\\sqrt'" +
" at position 10: 1^{2\\sqrt}̲");
});
it("complains about functions as arguments to others", function() {
// TODO: The position looks pretty wrong here
expect("\\sqrt\\over2").toFailWithParseError(
expect`\sqrt\over2`.toFailWithParseError(
"Got function '\\over' as argument to '\\sqrt'" +
" at position 6: \\sqrt\\̲o̲v̲e̲r̲2");
});
@@ -115,17 +115,17 @@ describe("Parser:", function() {
describe("#parseArguments", function() {
it("complains about missing argument at end of input", function() {
expect("2\\sqrt").toFailWithParseError(
expect`2\sqrt`.toFailWithParseError(
"Expected group after '\\sqrt' at end of input: 2\\sqrt");
});
it("complains about missing argument at end of group", function() {
expect("1^{2\\sqrt}").toFailWithParseError(
expect`1^{2\sqrt}`.toFailWithParseError(
"Expected group after '\\sqrt'" +
" at position 10: 1^{2\\sqrt}̲");
});
it("complains about functions as arguments to others", function() {
// TODO: The position looks pretty wrong here
expect("\\sqrt\\over2").toFailWithParseError(
expect`\sqrt\over2`.toFailWithParseError(
"Got function '\\over' as argument to '\\sqrt'" +
" at position 6: \\sqrt\\̲o̲v̲e̲r̲2");
});
@@ -133,7 +133,7 @@ describe("Parser:", function() {
describe("#verb", function() {
it("complains about mismatched \\verb with end of string", function() {
expect("\\verb|hello").toFailWithParseError(
expect`\verb|hello`.toFailWithParseError(
"\\verb ended by end of line instead of matching delimiter");
});
it("complains about mismatched \\verb with end of line", function() {
@@ -148,28 +148,28 @@ describe("Parser.expect calls:", function() {
describe("#parseInput expecting EOF", function() {
it("complains about extra }", function() {
expect("{1+2}}").toFailWithParseError(
expect`{1+2}}`.toFailWithParseError(
"Expected 'EOF', got '}' at position 6: {1+2}}̲");
});
it("complains about extra \\end", function() {
expect("x\\end{matrix}").toFailWithParseError(
expect`x\end{matrix}`.toFailWithParseError(
"Expected 'EOF', got '\\end' at position 2:" +
" x\\̲e̲n̲d̲{matrix}");
});
it("complains about top-level &", function() {
expect("1&2").toFailWithParseError(
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(
expect`\left(1+2)`.toFailWithParseError(
"Expected '\\right', got 'EOF' at end of input:" +
" \\left(1+2)");
});
it("rejects incorrectly scoped \\right", function() {
expect("{\\left(1+2}\\right)").toFailWithParseError(
expect`{\left(1+2}\right)`.toFailWithParseError(
"Expected '\\right', got '}' at position 11:" +
" {\\left(1+2}̲\\right)");
});
@@ -180,32 +180,32 @@ describe("Parser.expect calls:", function() {
describe("#parseSpecialGroup expecting braces", function() {
it("complains about missing { for color", function() {
expect("\\textcolor#ffffff{text}").toFailWithParseError(
expect`\textcolor#ffffff{text}`.toFailWithParseError(
"Expected '{', got '#' at position 11:" +
" \\textcolor#̲ffffff{text}");
});
it("complains about missing { for size", function() {
expect("\\rule{1em}[2em]").toFailWithParseError(
expect`\rule{1em}[2em]`.toFailWithParseError(
"Invalid size: '[' 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("\\textcolor{#ffffff{text}").toFailWithParseError(
expect`\textcolor{#ffffff{text}`.toFailWithParseError(
"Invalid color: '#ffffff{text' at position 12:" +
" \\textcolor{#̲f̲f̲f̲f̲f̲f̲{̲t̲e̲x̲t̲}");
});
it("complains about missing ] for size", function() {
expect("\\rule[1em{2em}{3em}").toFailWithParseError(
expect`\rule[1em{2em}{3em}`.toFailWithParseError(
"Unexpected end of input in size" +
" at position 7: \\rule[1̲e̲m̲{̲2̲e̲m̲}̲{̲3̲e̲m̲}̲");
});
it("complains about missing ] for size at end of input", function() {
expect("\\rule[1em").toFailWithParseError(
expect`\rule[1em`.toFailWithParseError(
"Unexpected end of input in size" +
" at position 7: \\rule[1̲e̲m̲");
});
it("complains about missing } for color at end of input", function() {
expect("\\textcolor{#123456").toFailWithParseError(
expect`\textcolor{#123456`.toFailWithParseError(
"Unexpected end of input in color" +
" at position 12: \\textcolor{#̲1̲2̲3̲4̲5̲6̲");
});
@@ -213,18 +213,18 @@ describe("Parser.expect calls:", function() {
describe("#parseGroup expecting }", function() {
it("at end of file", function() {
expect("\\sqrt{2").toFailWithParseError(
expect`\sqrt{2`.toFailWithParseError(
"Expected '}', got 'EOF' at end of input: \\sqrt{2");
});
});
describe("#parseOptionalGroup expecting ]", function() {
it("at end of file", function() {
expect("\\sqrt[3").toFailWithParseError(
expect`\sqrt[3`.toFailWithParseError(
"Expected ']', got 'EOF' at end of input: \\sqrt[3");
});
it("before group", function() {
expect("\\sqrt[3{2}").toFailWithParseError(
expect`\sqrt[3{2}`.toFailWithParseError(
"Expected ']', got 'EOF' at end of input: \\sqrt[3{2}");
});
});
@@ -235,12 +235,12 @@ describe("environments.js:", function() {
describe("parseArray", function() {
it("rejects missing \\end", function() {
expect("\\begin{matrix}1").toFailWithParseError(
expect`\begin{matrix}1`.toFailWithParseError(
"Expected & or \\\\ or \\cr or \\end at end of input:" +
" \\begin{matrix}1");
});
it("rejects incorrectly scoped \\end", function() {
expect("{\\begin{matrix}1}\\end{matrix}").toFailWithParseError(
expect`{\begin{matrix}1}\end{matrix}`.toFailWithParseError(
"Expected & or \\\\ or \\cr or \\end at position 17:" +
" …\\begin{matrix}1}̲\\end{matrix}");
});
@@ -249,7 +249,7 @@ describe("environments.js:", function() {
describe("array environment", function() {
it("rejects unknown column types", function() {
// TODO: The error position here looks strange
expect("\\begin{array}{cba}\\end{array}").toFailWithParseError(
expect`\begin{array}{cba}\end{array}`.toFailWithParseError(
"Unknown column alignment: b at position 16:" +
" \\begin{array}{cb̲a}\\end{array}");
});
@@ -261,12 +261,12 @@ describe("functions.js:", function() {
describe("delimiter functions", function() {
it("reject invalid opening delimiters", function() {
expect("\\bigl 1 + 2 \\bigr").toFailWithParseError(
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(
expect`\bigl(1+2\bigr=3`.toFailWithParseError(
"Invalid delimiter: '=' after '\\bigr' at position 15:" +
" \\bigl(1+2\\bigr=̲3");
});
@@ -274,7 +274,7 @@ describe("functions.js:", function() {
describe("\\begin and \\end", function() {
it("reject invalid environment names", function() {
expect("\\begin x\\end y").toFailWithParseError(
expect`\begin x\end y`.toFailWithParseError(
"Invalid environment name at position 8: \\begin x̲\\end y");
});
});
@@ -297,7 +297,7 @@ describe("Lexer:", function() {
describe("#_innerLexColor", function() {
it("reject hex notation without #", function() {
expect("\\textcolor{1a2b3c}{foo}").toFailWithParseError(
expect`\textcolor{1a2b3c}{foo}`.toFailWithParseError(
"Invalid color: '1a2b3c'" +
" at position 12: \\textcolor{1̲a̲2̲b̲3̲c̲}{foo}");
});
@@ -305,15 +305,15 @@ describe("Lexer:", function() {
describe("#_innerLexSize", function() {
it("reject size without unit", function() {
expect("\\rule{0}{2em}").toFailWithParseError(
expect`\rule{0}{2em}`.toFailWithParseError(
"Invalid size: '0' at position 7: \\rule{0̲}{2em}");
});
it("reject size with bogus unit", function() {
expect("\\rule{1au}{2em}").toFailWithParseError(
expect`\rule{1au}{2em}`.toFailWithParseError(
"Invalid unit: 'au' at position 7: \\rule{1̲a̲u̲}{2em}");
});
it("reject size without number", function() {
expect("\\rule{em}{2em}").toFailWithParseError(
expect`\rule{em}{2em}`.toFailWithParseError(
"Invalid size: 'em' at position 7: \\rule{e̲m̲}{2em}");
});
});

View File

@@ -1,14 +1,73 @@
/* global expect: false */
import katex from "../katex";
import ParseError from "../src/ParseError";
import parseTree from "../src/parseTree";
import Settings from "../src/Settings";
import diff from 'jest-diff';
import {RECEIVED_COLOR, printReceived, printExpected} from 'jest-matcher-utils';
import {formatStackTrace, separateMessageFromStack} from 'jest-message-util';
export function ConsoleWarning(message) {
Error.captureStackTrace(this, global.console.warn);
this.name = this.constructor.name;
this.message = message;
}
Object.setPrototypeOf(ConsoleWarning.prototype, Error.prototype);
/**
* Return the first raw string if x is tagged literal. Otherwise return x.
*/
export const r = x => x != null && x.hasOwnProperty('raw') ? x.raw[0] : x;
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This function is from https://github.com/facebook/jest/blob/9867e16e518d50c79
* 492f7f0d2bc1ef8dff37db4/packages/expect/src/to_throw_matchers.js and licensed
* under the MIT license found in the https://github.com/facebook/jest/blob/master/LICENSE.
*/
const printActualErrorMessage = error => {
if (error) {
const {message, stack} = separateMessageFromStack(error.stack);
return (
'Instead, it threw:\n' +
RECEIVED_COLOR(
` ${message}` +
formatStackTrace(
// remove KaTeX internal stack entries
stack.split('\n')
.filter(line => line.indexOf('new ParseError') === -1)
.join('\n'),
{
rootDir: process.cwd(),
testMatch: [],
},
{
noStackTrace: false,
},
),
)
);
}
return 'But it didn\'t throw anything.';
};
const printExpectedResult = (mode, isNot, expectedError) => expectedError == null
? (isNot ? 'fail ' : 'success ') + mode
: (isNot ? 'not throw a ' : `fail ${mode} with a `) +
(expectedError.name || `ParseError matching "${expectedError}"`);
export const nonstrictSettings = new Settings({strict: false});
export const strictSettings = new Settings({strict: true});
export const _getBuilt = function(expr, settings = new Settings()) {
/**
* Return the root node of the rendered HTML.
* @param expr
* @param settings
* @returns {Object}
*/
export function getBuilt(expr, settings = new Settings()) {
expr = r(expr); // support tagging literals
let rootNode = katex.__renderToDomTree(expr, settings);
if (rootNode.classes.indexOf('katex-error') >= 0) {
@@ -30,18 +89,7 @@ export const _getBuilt = function(expr, settings = new Settings()) {
(node) => node.classes.indexOf("strut") < 0));
}
return children;
};
/**
* Return the root node of the rendered HTML.
* @param expr
* @param settings
* @returns {Object}
*/
export const getBuilt = function(expr, settings = new Settings()) {
expect(expr).toBuild(settings);
return _getBuilt(expr, settings);
};
}
/**
* Return the root node of the parse tree.
@@ -49,12 +97,12 @@ export const getBuilt = function(expr, settings = new Settings()) {
* @param settings
* @returns {Object}
*/
export const getParsed = function(expr, settings = new Settings()) {
expect(expr).toParse(settings);
export function getParsed(expr, settings = new Settings()) {
expr = r(expr); // support tagging literals
return parseTree(expr, settings);
};
}
export const stripPositions = function(expr) {
export const stripPositions = expr => {
if (typeof expr !== "object" || expr === null) {
return expr;
}
@@ -67,34 +115,63 @@ export const stripPositions = function(expr) {
return expr;
};
export const parseAndSetResult = function(expr, result,
settings = new Settings()) {
try {
return parseTree(expr, settings);
} 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}`;
}
}
export const Mode = {
PARSE: {
apply: getParsed,
noun: 'parsing',
Verb: 'Parse',
},
BUILD: {
apply: getBuilt,
noun: 'building',
Verb: 'Build',
},
};
export const buildAndSetResult = function(expr, result,
settings = new Settings()) {
export const expectKaTeX = (expr, settings, mode, isNot, expectedError) => {
let pass = expectedError == null;
let error;
try {
return _getBuilt(expr, settings);
mode.apply(expr, settings);
} catch (e) {
result.pass = false;
error = e;
if (e instanceof ParseError) {
result.message = () =>
`'${expr}' failed building with error: ${e.message}`;
pass = expectedError === ParseError || (typeof expectedError ===
"string" && e.message === `KaTeX parse error: ${expectedError}`);
} else if (e instanceof ConsoleWarning) {
pass = expectedError === ConsoleWarning;
} else {
result.message = () =>
`'${expr}' failed building with unknown error: ${e.message}`;
pass = !!isNot; // always fail
}
}
return {
pass,
message: () => 'Expected the expression to ' +
printExpectedResult(mode.noun, isNot, expectedError) +
`:\n ${printReceived(expr)}\n` +
printActualErrorMessage(error),
};
};
export const expectEquivalent = (actual, expected, settings, mode, expand) => {
const actualTree = stripPositions(mode.apply(actual, settings));
const expectedTree = stripPositions(mode.apply(expected, settings));
const pass = JSON.stringify(actualTree) === JSON.stringify(expectedTree);
return {
pass,
message: pass
? () =>
`${mode.Verb} trees of ${printReceived(actual)} and ` +
`${printExpected(expected)} are equivalent`
: () => {
const diffString = diff(expectedTree, actualTree, {
expand,
});
return `${mode.Verb} trees of ${printReceived(actual)} and ` +
`${printExpected(expected)} are not equivalent` +
(diffString ? `:\n\n${diffString}` : '');
},
};
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,10 @@
/* global jest: false */
/* global expect: false */
import katex from "../katex";
import ParseError from "../src/ParseError";
import parseTree from "../src/parseTree";
import Settings from "../src/Settings";
import Warning from "./Warning";
import stringify from 'json-stable-stringify';
import ParseError from "../src/ParseError";
import {
_getBuilt, buildAndSetResult, parseAndSetResult, stripPositions,
Mode, ConsoleWarning,
expectKaTeX, expectEquivalent,
} from "./helpers";
// Serializer support
@@ -43,192 +39,33 @@ const serializer = {
expect.addSnapshotSerializer(serializer);
// Turn warnings into errors
global.console.warn = jest.fn((warning) => {
throw new Warning(warning);
});
// Mock console.warn to throw an error
global.console.warn = x => { throw new ConsoleWarning(x); };
// Expect extensions
expect.extend({
toParse: function(actual, settings = new Settings()) {
const result = {
pass: true,
message: () => `'${actual}' succeeded parsing`,
};
parseAndSetResult(actual, result, settings);
return result;
toParse(expr, settings) {
return expectKaTeX(expr, settings, Mode.PARSE, this.isNot);
},
toNotParse: function(actual, settings = new Settings()) {
const result = {
pass: false,
message: () =>
`Expected '${actual}' to fail parsing, but it succeeded`,
};
try {
parseTree(actual, settings);
} 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;
toFailWithParseError: function(expr, expected = ParseError) {
return expectKaTeX(expr, undefined, Mode.PARSE, this.isNot, expected);
},
toFailWithParseError: function(actual, expected) {
const prefix = "KaTeX parse error: ";
try {
parseTree(actual, new Settings());
return {
pass: false,
message: () => `'${actual}' parsed without error`,
};
} catch (e) {
if (expected === undefined) {
return {
pass: true,
message: () => `'${actual}' parsed with error`,
};
}
const msg = e.message;
const exp = prefix + expected;
if (msg === exp) {
return {
pass: true,
message: () =>
`'${actual}' parsed with expected 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}'`,
};
}
}
toBuild(expr, settings) {
return expectKaTeX(expr, settings, Mode.BUILD, this.isNot);
},
toBuild: function(actual, settings = new Settings()) {
const result = {
pass: true,
message: () => `'${actual}' succeeded in building`,
};
buildAndSetResult(actual, result, settings);
return result;
toWarn(expr, settings) {
return expectKaTeX(expr, settings, Mode.BUILD, this.isNot, ConsoleWarning);
},
toNotBuild: function(actual, settings = new Settings()) {
const result = {
pass: false,
message: () =>
`Expected '${actual}' to fail building, but it succeeded`,
};
try {
_getBuilt(actual, settings);
} catch (e) {
if (e instanceof ParseError) {
result.pass = true;
result.message = () => `'${actual}' correctly ` +
`didn't build with error: ${e.message}`;
} else {
result.message = () => `'${actual}' failed ` +
`building with unknown error: ${e.message}`;
}
}
return result;
toParseLike(expr, expected, settings) {
return expectEquivalent(expr, expected, settings, Mode.PARSE, this.expand);
},
toParseLike: function(actual, expected, settings = new Settings()) {
const result = {
pass: true,
message: () =>
`Parse trees of '${actual}' and '${expected}' are equivalent`,
};
const actualTree = parseAndSetResult(actual, result, settings);
if (!actualTree) {
return result;
}
const expectedTree = parseAndSetResult(expected, result, settings);
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 = new Settings()) {
const result = {
pass: true,
message: () =>
`Build trees of '${actual}' and '${expected}' are equivalent`,
};
const actualTree = buildAndSetResult(actual, result, settings);
if (!actualTree) {
return result;
}
const expectedTree = buildAndSetResult(expected, result, settings);
if (!expectedTree) {
return result;
}
stripPositions(actualTree);
stripPositions(expectedTree);
if (JSON.stringify(actualTree) !== JSON.stringify(expectedTree)) {
result.pass = false;
result.message = () => `Build trees of '${actual}' and ` +
`'${expected}' are not equivalent`;
}
return result;
},
toWarn: function(actual, settings = new Settings()) {
const result = {
pass: false,
message: () =>
`Expected '${actual}' to generate a warning, but it succeeded`,
};
try {
katex.__renderToDomTree(actual, settings);
} catch (e) {
if (e instanceof Warning) {
result.pass = true;
result.message = () =>
`'${actual}' correctly generated warning: ${e.message}`;
} else {
result.message = () =>
`'${actual}' failed building with unknown error: ${e.message}`;
}
}
return result;
toBuildLike(expr, expected, settings) {
return expectEquivalent(expr, expected, settings, Mode.BUILD, this.expand);
},
});

View File

@@ -8,92 +8,92 @@ import {strictSettings, nonstrictSettings} from "./helpers";
describe("unicode", function() {
it("should parse Latin-1 inside \\text{}", function() {
expect('\\text{ÀÁÂÃÄÅÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåèéêëìíîïñòóôõöùúûüýÿ' +
'ÆÇÐØÞßæçðøþ}').toParse();
expect`\text{ÀÁÂÃÄÅÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåèéêëìíîïñòóôõöùúûüýÿÆÇÐØÞßæçðøþ}`
.toParse();
});
it("should not parse Latin-1 outside \\text{} with strict", function() {
const chars = 'ÀÁÂÃÄÅÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåèéêëìíîïñòóôõöùúûüýÿÇÐÞçþ';
for (const ch of chars) {
expect(ch).toNotParse(strictSettings);
expect(ch).not.toParse(strictSettings);
}
});
it("should parse Latin-1 outside \\text{}", function() {
expect('ÀÁÂÃÄÅÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåèéêëìíîïñòóôõöùúûüýÿ' +
'ÇÐÞçðþ').toParse(nonstrictSettings);
expect`ÀÁÂÃÄÅÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåèéêëìíîïñòóôõöùúûüýÿÇÐÞçðþ`
.toParse(nonstrictSettings);
});
it("should parse all lower case Greek letters", function() {
expect("αβγδεϵζηθϑικλμνξοπϖρϱςστυφϕχψω").toParse();
expect`αβγδεϵζηθϑικλμνξοπϖρϱςστυφϕχψω`.toParse();
});
it("should parse math upper case Greek letters", function() {
expect("ΓΔΘΛΞΠΣΥΦΨΩ").toParse();
expect`ΓΔΘΛΞΠΣΥΦΨΩ`.toParse();
});
it("should parse Cyrillic inside \\text{}", function() {
expect('\\text{БГДЖЗЙЛФЦШЫЮЯ}').toParse();
expect`\text{БГДЖЗЙЛФЦШЫЮЯ}`.toParse();
});
it("should not parse Cyrillic outside \\text{} with strict", function() {
expect('БГДЖЗЙЛФЦШЫЮЯ').toNotParse(strictSettings);
expect`БГДЖЗЙЛФЦШЫЮЯ`.not.toParse(strictSettings);
});
it("should parse CJK inside \\text{}", function() {
expect('\\text{私はバナナです}').toParse();
expect('\\text{여보세요}').toParse();
expect`\text{私はバナナです}`.toParse();
expect`\text{여보세요}`.toParse();
});
it("should not parse CJK outside \\text{} with strict", function() {
expect('私はバナナです。').toNotParse(strictSettings);
expect('여보세요').toNotParse(strictSettings);
expect`私はバナナです。`.not.toParse(strictSettings);
expect`여보세요`.not.toParse(strictSettings);
});
it("should parse Devangari inside \\text{}", function() {
expect('\\text{नमस्ते}').toParse();
expect`\text{नमस्ते}`.toParse();
});
it("should not parse Devangari outside \\text{} with strict", function() {
expect('नमस्ते').toNotParse(strictSettings);
expect`नमस्ते`.not.toParse(strictSettings);
});
it("should parse Georgian inside \\text{}", function() {
expect('\\text{გამარჯობა}').toParse();
expect`\text{გამარჯობა}`.toParse();
});
it("should not parse Georgian outside \\text{} with strict", function() {
expect('გამარჯობა').toNotParse(strictSettings);
expect`გამარჯობა`.not.toParse(strictSettings);
});
it("should parse extended Latin characters inside \\text{}", function() {
expect('\\text{ěščřžůřťďňőİı}').toParse();
expect`\text{ěščřžůřťďňőİı}`.toParse();
});
it("should not parse extended Latin outside \\text{} with strict", function() {
expect('ěščřžůřťďňőİı').toNotParse(strictSettings);
expect`ěščřžůřťďňőİı`.not.toParse(strictSettings);
});
it("should not allow emoji in strict mode", function() {
expect('✌').toNotParse(strictSettings);
expect('\\text{✌}').toNotParse(strictSettings);
expect``.not.toParse(strictSettings);
expect`\text{✌}`.not.toParse(strictSettings);
const settings = new Settings({
strict: (errorCode) =>
(errorCode === "unknownSymbol" ? "error" : "ignore"),
});
expect('✌').toNotParse(settings);
expect('\\text{✌}').toNotParse(settings);
expect``.not.toParse(settings);
expect`\text{✌}`.not.toParse(settings);
});
it("should allow emoji outside strict mode", function() {
expect('✌').toWarn();
expect('\\text{✌}').toWarn();
expect``.toWarn();
expect`\text{✌}`.toWarn();
const settings = new Settings({
strict: (errorCode) =>
(errorCode === "unknownSymbol" ? "ignore" : "error"),
});
expect('✌').toParse(settings);
expect('\\text{✌}').toParse(settings);
expect``.toParse(settings);
expect`\text{✌}`.toParse(settings);
});
});