mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-07 12:18:39 +00:00
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:
committed by
Kevin Barabash
parent
c9947220b6
commit
71035c7111
@@ -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;
|
@@ -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": {
|
||||
|
@@ -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}");
|
||||
});
|
||||
});
|
||||
|
159
test/helpers.js
159
test/helpers.js
@@ -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}` : '');
|
||||
},
|
||||
};
|
||||
};
|
||||
|
1702
test/katex-spec.js
1702
test/katex-spec.js
File diff suppressed because it is too large
Load Diff
197
test/setup.js
197
test/setup.js
@@ -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);
|
||||
},
|
||||
});
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user