Move test helpers into common modules (#1318)

* Move test helpers into common modules

* helpers.js gets all the helper functions
* setup.js gets the common Jest setup (serializer, expect extensions)

* Exclude test from coverage testing

* @ylemkimon's comments: parsing -> building, settings || defaultSettings

* Default argument for settings

* Fix lint errors

* @ylemklemon's comment: use buildAndSetResult

* Use template literals
This commit is contained in:
Erik Demaine
2018-05-18 09:45:19 -04:00
committed by GitHub
parent 4a3d6a526f
commit 7b22eeb64a
7 changed files with 308 additions and 392 deletions

View File

@@ -87,6 +87,10 @@
"nomnom": "^1.8.1"
},
"jest": {
"collectCoverageFrom": [
"src/**/*.js",
"contrib/**/*.js"
],
"setupTestFrameworkScriptFile": "<rootDir>/test/setup.js",
"snapshotSerializers": [
"jest-serializer-html"

View File

@@ -32,7 +32,16 @@ exports[`A parser that does not throw on unsupported commands should build katex
}
`;
exports[`A parser that does not throw on unsupported commands should properly escape LaTeX in errors 1`] = `"<span class=\\"katex-error\\" title=\\"ParseError: KaTeX parse error: Expected group after &#x27;^&#x27; at position 2: 2^̲&amp;&quot;&lt;&gt;\\" style=\\"color:#933\\">2^&amp;&quot;&lt;&gt;</span>"`;
exports[`A parser that does not throw on unsupported commands should properly escape LaTeX in errors 1`] = `
<span class="katex-error"
title="ParseError: KaTeX parse error: Expected group after &#x27;^&#x27; at position 2: 2^̲&amp;&quot;&lt;&gt;"
style="color:#933"
>
2^&amp;&quot;&lt;&gt;
</span>
`;
exports[`An implicit group parser within optional groups should work style commands \\sqrt[\\textstyle 3]{x} 1`] = `
[

View File

@@ -1,59 +1,7 @@
/* global beforeEach: false */
/* global expect: false */
/* global it: false */
/* global describe: false */
import parseTree from "../src/parseTree";
import Settings from "../src/Settings";
const defaultSettings = new Settings({});
beforeEach(function() {
const prefix = "KaTeX parse error: ";
expect.extend({
toFailWithParseError: 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",
};
}
const msg = e.message;
const 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() {

97
test/helpers.js Normal file
View File

@@ -0,0 +1,97 @@
/* global expect: false */
import katex from "../katex";
import ParseError from "../src/ParseError";
import parseTree from "../src/parseTree";
import Settings from "../src/Settings";
export const defaultSettings = new Settings({
strict: false, // deal with warnings only when desired
});
export const strictSettings = new Settings({strict: true});
export const _getBuilt = function(expr, settings = defaultSettings) {
const rootNode = katex.__renderToDomTree(expr, settings);
if (rootNode.classes.indexOf('katex-error') >= 0) {
return rootNode;
}
// grab the root node of the HTML rendering
const builtHTML = rootNode.children[1];
// combine the non-strut children of all base spans
const children = [];
for (let i = 0; i < builtHTML.children.length; i++) {
children.push(...builtHTML.children[i].children.filter(
(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 = defaultSettings) {
expect(expr).toBuild(settings);
return _getBuilt(expr, settings);
};
/**
* Return the root node of the parse tree.
* @param expr
* @param settings
* @returns {Object}
*/
export const getParsed = function(expr, settings = defaultSettings) {
expect(expr).toParse(settings);
return parseTree(expr, settings);
};
export const stripPositions = function(expr) {
if (typeof expr !== "object" || expr === null) {
return expr;
}
if (expr.loc && expr.loc.lexer && typeof expr.loc.start === "number") {
delete expr.loc;
}
Object.keys(expr).forEach(function(key) {
stripPositions(expr[key]);
});
return expr;
};
export const parseAndSetResult = function(expr, result,
settings = defaultSettings) {
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 buildAndSetResult = function(expr, result,
settings = defaultSettings) {
try {
return _getBuilt(expr, settings);
} catch (e) {
result.pass = false;
if (e instanceof ParseError) {
result.message = () =>
`'${expr}' failed building with error: ${e.message}`;
} else {
result.message = () =>
`'${expr}' failed building with unknown error: ${e.message}`;
}
}
};

View File

@@ -1,290 +1,26 @@
/* eslint max-len:0 */
/* global beforeEach: false */
/* global expect: false */
/* global it: false */
/* global describe: false */
import stringify from 'json-stable-stringify';
import buildMathML from "../src/buildMathML";
import buildTree from "../src/buildTree";
import katex from "../katex";
import ParseError from "../src/ParseError";
import parseTree from "../src/parseTree";
import Options from "../src/Options";
import Settings from "../src/Settings";
import Style from "../src/Style";
import {
defaultSettings,
_getBuilt, getBuilt, getParsed, stripPositions,
} from "./helpers";
const typeFirstCompare = (a, b) => {
if (a.key === 'type') {
return -1;
} else if (b.key === 'type') {
return 1;
} else {
return a.key < b.key ? -1 : 1;
}
};
const serializer = {
print(val) {
return stringify(val, {cmp: typeFirstCompare, space: ' '});
},
test() {
return true;
},
};
expect.addSnapshotSerializer(serializer);
const defaultSettings = new Settings({
strict: false, // deal with warnings only when desired
});
const defaultOptions = new Options({
style: Style.TEXT,
size: 5,
maxSize: Infinity,
});
const _getBuilt = function(expr, settings) {
const usedSettings = settings ? settings : defaultSettings;
const rootNode = katex.__renderToDomTree(expr, usedSettings);
if (rootNode.classes.indexOf('katex-error') >= 0) {
return rootNode;
}
// grab the root node of the HTML rendering
const builtHTML = rootNode.children[1];
// combine the non-strut children of all base spans
const children = [];
for (let i = 0; i < builtHTML.children.length; i++) {
children.push(...builtHTML.children[i].children.filter(
(node) => node.classes.indexOf("strut") < 0));
}
return children;
};
/**
* Return the root node of the rendered HTML.
* @param expr
* @param settings
* @returns {Object}
*/
const getBuilt = function(expr, settings) {
const usedSettings = settings ? settings : defaultSettings;
expect(expr).toBuild(usedSettings);
return _getBuilt(expr, settings);
};
/**
* Return the root node of the parse tree.
* @param expr
* @param settings
* @returns {Object}
*/
const getParsed = function(expr, settings) {
const usedSettings = settings ? settings : defaultSettings;
expect(expr).toParse(usedSettings);
return parseTree(expr, usedSettings);
};
const stripPositions = function(expr) {
if (typeof expr !== "object" || expr === null) {
return expr;
}
if (expr.loc && expr.loc.lexer && typeof expr.loc.start === "number") {
delete expr.loc;
}
Object.keys(expr).forEach(function(key) {
stripPositions(expr[key]);
});
return expr;
};
const parseAndSetResult = function(expr, result, settings) {
try {
return parseTree(expr, settings || defaultSettings);
} 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;
}
}
};
const buildAndSetResult = function(expr, result, settings) {
try {
return _getBuilt(expr, settings || defaultSettings);
} 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;
}
}
};
beforeEach(function() {
expect.extend({
toParse: function(actual, settings) {
const usedSettings = settings ? settings : defaultSettings;
const result = {
pass: true,
message: () => "'" + actual + "' succeeded parsing",
};
parseAndSetResult(actual, result, usedSettings);
return result;
},
toNotParse: function(actual, settings) {
const usedSettings = settings ? settings : defaultSettings;
const result = {
pass: false,
message: () => "Expected '" + actual + "' to fail " +
"parsing, but it succeeded",
};
try {
parseTree(actual, usedSettings);
} 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;
},
toBuild: function(actual, settings) {
const usedSettings = settings ? settings : defaultSettings;
const result = {
pass: true,
message: () => "'" + actual + "' succeeded in building",
};
expect(actual).toParse(usedSettings);
try {
_getBuilt(actual, usedSettings);
} catch (e) {
result.pass = false;
if (e instanceof ParseError) {
result.message = () => "'" + actual + "' failed to " +
"build with error: " + e.message;
} else {
result.message = () => "'" + actual + "' failed " +
"building with unknown error: " + e.message;
}
}
return result;
},
toNotBuild: function(actual, settings) {
const usedSettings = settings ? settings : defaultSettings;
const result = {
pass: false,
message: () => "Expected '" + actual + "' to fail " +
"building, but it succeeded",
};
try {
_getBuilt(actual, usedSettings);
} 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: function(actual, expected, settings) {
const usedSettings = settings ? settings : defaultSettings;
const result = {
pass: true,
message: () => "Parse trees of '" + actual +
"' and '" + expected + "' are equivalent",
};
const actualTree = parseAndSetResult(actual, result,
usedSettings);
if (!actualTree) {
return result;
}
const expectedTree = parseAndSetResult(expected, result,
usedSettings);
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) {
const usedSettings = settings ? settings : defaultSettings;
const result = {
pass: true,
message: () => "Build trees of '" + actual +
"' and '" + expected + "' are equivalent",
};
const actualTree = buildAndSetResult(actual, result,
usedSettings);
if (!actualTree) {
return result;
}
const expectedTree = buildAndSetResult(expected, result,
usedSettings);
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;
},
});
});
describe("A parser", function() {
it("should not fail on an empty string", function() {
expect("").toParse();

View File

@@ -2,21 +2,206 @@
/* global expect: false */
import katex from "../katex";
import Settings from "../src/Settings";
import ParseError from "../src/ParseError";
import parseTree from "../src/parseTree";
import Warning from "./Warning";
import stringify from 'json-stable-stringify';
import {
defaultSettings,
_getBuilt, buildAndSetResult, parseAndSetResult, stripPositions,
} from "./helpers";
// Serializer support
const typeFirstCompare = (a, b) => {
if (a.key === 'type') {
return -1;
} else if (b.key === 'type') {
return 1;
} else {
return a.key < b.key ? -1 : 1;
}
};
const serializer = {
print(val) {
return stringify(val, {cmp: typeFirstCompare, space: ' '});
},
test(val) {
// Leave strings (e.g. XML) to other serializers
return typeof val !== "string";
},
};
expect.addSnapshotSerializer(serializer);
// Turn warnings into errors
global.console.warn = jest.fn((warning) => {
throw new Warning(warning);
});
const defaultSettings = new Settings({
strict: false, // enable dealing with warnings only when needed
});
// Expect extensions
expect.extend({
toWarn: function(actual, settings) {
const usedSettings = settings ? settings : defaultSettings;
toParse: function(actual, settings = defaultSettings) {
const result = {
pass: true,
message: () => `'${actual}' succeeded parsing`,
};
parseAndSetResult(actual, result, settings);
return result;
},
toNotParse: function(actual, settings = defaultSettings) {
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(actual, expected) {
const prefix = "KaTeX parse error: ";
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`,
};
}
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: function(actual, settings = defaultSettings) {
const result = {
pass: true,
message: () => `'${actual}' succeeded in building`,
};
buildAndSetResult(actual, result, settings);
return result;
},
toNotBuild: function(actual, settings = defaultSettings) {
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: function(actual, expected, settings = defaultSettings) {
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 = defaultSettings) {
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 = defaultSettings) {
const result = {
pass: false,
message: () =>
@@ -24,7 +209,7 @@ expect.extend({
};
try {
katex.__renderToDomTree(actual, usedSettings);
katex.__renderToDomTree(actual, settings);
} catch (e) {
if (e instanceof Warning) {
result.pass = true;

View File

@@ -1,75 +1,12 @@
/* eslint max-len:0 */
/* global beforeEach: false */
/* global expect: false */
/* global it: false */
/* global describe: false */
import ParseError from "../src/ParseError";
import parseTree from "../src/parseTree";
import Settings from "../src/Settings";
import {scriptFromCodepoint, supportedCodepoint} from "../src/unicodeScripts";
const defaultSettings = new Settings({
strict: false, // deal with warnings only when desired
});
const strictSettings = new Settings({strict: true});
const parseAndSetResult = function(expr, result, settings) {
try {
return parseTree(expr, settings || defaultSettings);
} 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;
}
}
};
import {strictSettings} from "./helpers";
describe("unicode", function() {
beforeEach(function() {
expect.extend({
toParse: function(actual, settings) {
const usedSettings = settings ? settings : defaultSettings;
const result = {
pass: true,
message: () => "'" + actual + "' succeeded parsing",
};
parseAndSetResult(actual, result, usedSettings);
return result;
},
toNotParse: function(actual, settings) {
const usedSettings = settings ? settings : defaultSettings;
const result = {
pass: false,
message: () => "Expected '" + actual + "' to fail " +
"parsing, but it succeeded",
};
try {
parseTree(actual, usedSettings);
} 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;
},
});
});
it("should parse Latin-1 inside \\text{}", function() {
expect('\\text{ÀÁÂÃÄÅÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåèéêëìíîïñòóôõöùúûüýÿ' +
'ÆÇÐØÞßæçðøþ}').toParse();