From 7b22eeb64ad7acf0090a393ba339f044e5886c15 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Fri, 18 May 2018 09:45:19 -0400 Subject: [PATCH] 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 --- package.json | 4 + test/__snapshots__/katex-spec.js.snap | 11 +- test/errors-spec.js | 52 ----- test/helpers.js | 97 +++++++++ test/katex-spec.js | 272 +------------------------- test/setup.js | 199 ++++++++++++++++++- test/unicode-spec.js | 65 +----- 7 files changed, 308 insertions(+), 392 deletions(-) create mode 100644 test/helpers.js diff --git a/package.json b/package.json index 382b8c26..f1efc33d 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,10 @@ "nomnom": "^1.8.1" }, "jest": { + "collectCoverageFrom": [ + "src/**/*.js", + "contrib/**/*.js" + ], "setupTestFrameworkScriptFile": "/test/setup.js", "snapshotSerializers": [ "jest-serializer-html" diff --git a/test/__snapshots__/katex-spec.js.snap b/test/__snapshots__/katex-spec.js.snap index 612fa342..930b7b17 100644 --- a/test/__snapshots__/katex-spec.js.snap +++ b/test/__snapshots__/katex-spec.js.snap @@ -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`] = `"2^&"<>"`; +exports[`A parser that does not throw on unsupported commands should properly escape LaTeX in errors 1`] = ` + + + 2^&"<> + + +`; exports[`An implicit group parser within optional groups should work style commands \\sqrt[\\textstyle 3]{x} 1`] = ` [ diff --git a/test/errors-spec.js b/test/errors-spec.js index a073f3c6..23c75b48 100644 --- a/test/errors-spec.js +++ b/test/errors-spec.js @@ -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() { diff --git a/test/helpers.js b/test/helpers.js new file mode 100644 index 00000000..c6062066 --- /dev/null +++ b/test/helpers.js @@ -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}`; + } + } +}; diff --git a/test/katex-spec.js b/test/katex-spec.js index 21e5aa94..331cc351 100644 --- a/test/katex-spec.js +++ b/test/katex-spec.js @@ -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(); diff --git a/test/setup.js b/test/setup.js index 7a49d84a..731309ca 100644 --- a/test/setup.js +++ b/test/setup.js @@ -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; diff --git a/test/unicode-spec.js b/test/unicode-spec.js index 2c56f346..1453231e 100644 --- a/test/unicode-spec.js +++ b/test/unicode-spec.js @@ -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();