Switch from jasmine to jest (#747)

Summary:
The reasons for switching to jest:
- easy snapshot testing so that we can easily verify the structure of the parse tree and MathML tree
- easy compilation of ES6 features for tests as we continue to expand our use of ES6

Test Plan:
- npm test
This commit is contained in:
Kevin Barabash
2017-07-11 14:16:26 -04:00
committed by Erik Demaine
parent 4480f2c987
commit 361c500a7f
12 changed files with 228 additions and 275 deletions

1
.gitignore vendored
View File

@@ -12,3 +12,4 @@ diff.png
/test/symgroups.log
/test/symgroups.pdf
/test/screenshotter/unicode-fonts
coverage/

View File

@@ -9,9 +9,9 @@ help solving a problem, feel free to stop by our [gitter channel](https://gitter
If you'd like to contribute, try contributing new symbols or functions that
KaTeX doesn't currently support. The wiki has a page which lists [all of the
supported
functions](https://github.com/Khan/KaTeX/wiki/Function-Support-in-KaTeX) as
well as a page that describes how to [examine TeX commands and where to find
rules](https://github.com/Khan/KaTeX/wiki/Examining-TeX) which can be quite
functions](https://github.com/Khan/KaTeX/wiki/Function-Support-in-KaTeX) as
well as a page that describes how to [examine TeX commands and where to find
rules](https://github.com/Khan/KaTeX/wiki/Examining-TeX) which can be quite
useful when adding new commands. There's also a user-contributed [preview page]
(http://utensil-site.github.io/available-in-katex/)
showing how KaTeX would render a series of symbols/functions (including the ones
@@ -60,18 +60,22 @@ This will host an interactive editor at
[http://localhost:7936/](http://localhost:7936/) to play around with and test
changes.
#### Jasmine tests
#### Jest tests
The JavaScript parser and some of the tree
builder is tested with Jasmine. These tests can be run either using node with
`make test`, or in the browser by visiting
[http://localhost:7936/test/test.html](http://localhost:7936/test/test.html).
The JavaScript parser and some of the HTML and MathML tree
builders are tested with Jest. These tests can be run using node with
`make test`. If you need to debug the tests see
[https://facebook.github.io/jest/docs/troubleshooting.html](https://facebook.github.io/jest/docs/troubleshooting.html)
The Jasmine tests should be run after every change, even the addition of small
The interactive editor can also be used for debugging tests in the browser by
copy/pasting the test case to be debugged into the editor. The permalink option
can come in really useful when doing repeated runs of the same test case.
The Jest tests should be run after every change, even the addition of small
symbols. However, [Travis](https://travis-ci.org/Khan/KaTeX/) will run these
tests when you submit a pull request, in case you forget.
If you make any changes to Parser.js, add Jasmine tests to ensure they work.
If you make any changes to Parser.js, add Jest tests to ensure they work.
#### Screenshot tests
@@ -117,7 +121,7 @@ Code
In general, try to make your code blend in with the surrounding code.
## Pull Requests
- link back to the original issue(s) whenever possible
- new commands should be added to the [wiki](https://github.com/Khan/KaTeX/wiki/Function-Support-in-KaTeX)
- commits should be squashed before merging

View File

@@ -1,4 +1,4 @@
.PHONY: build dist lint setup copy serve clean metrics test zip contrib
.PHONY: build dist lint setup copy serve clean metrics test coverage zip contrib
build: lint build/katex.min.js build/katex.min.css contrib zip compress
ifeq ($(KATEX_DIST),skip)
@@ -101,7 +101,10 @@ serve: $(NIS)
$(NODE) server.js
test: $(NIS)
JASMINE_CONFIG_PATH=test/jasmine.json node_modules/.bin/jasmine
node_modules/.bin/jest
coverage: $(NIS)
node_modules/.bin/jest --coverage
PERL=perl
PYTHON=$(shell python2 --version >/dev/null 2>&1 && echo python2 || echo python)

View File

@@ -1,5 +1,4 @@
/* global beforeEach: false */
/* global jasmine: false */
/* global expect: false */
/* global it: false */
/* global describe: false */
@@ -7,60 +6,56 @@
const splitAtDelimiters = require("./splitAtDelimiters");
beforeEach(function() {
jasmine.addMatchers({
toSplitInto: function() {
return {
compare: function(actual, left, right, result) {
const message = {
pass: true,
message: "'" + actual + "' split correctly",
};
const startData = [{type: "text", data: actual}];
const split =
splitAtDelimiters(startData, left, right, false);
if (split.length !== result.length) {
message.pass = false;
message.message = "Different number of splits: " +
split.length + " vs. " + result.length + " (" +
JSON.stringify(split) + " vs. " +
JSON.stringify(result) + ")";
return message;
}
for (let i = 0; i < split.length; i++) {
const real = split[i];
const correct = result[i];
let good = true;
let diff;
if (real.type !== correct.type) {
good = false;
diff = "type";
} else if (real.data !== correct.data) {
good = false;
diff = "data";
} else if (real.display !== correct.display) {
good = false;
diff = "display";
}
if (!good) {
message.pass = false;
message.message = "Difference at split " +
(i + 1) + ": " + JSON.stringify(real) +
" vs. " + JSON.stringify(correct) +
" (" + diff + " differs)";
break;
}
}
return message;
},
expect.extend({
toSplitInto: function(actual, left, right, result) {
const message = {
pass: true,
message: "'" + actual + "' split correctly",
};
const startData = [{type: "text", data: actual}];
const split =
splitAtDelimiters(startData, left, right, false);
if (split.length !== result.length) {
message.pass = false;
message.message = "Different number of splits: " +
split.length + " vs. " + result.length + " (" +
JSON.stringify(split) + " vs. " +
JSON.stringify(result) + ")";
return message;
}
for (let i = 0; i < split.length; i++) {
const real = split[i];
const correct = result[i];
let good = true;
let diff;
if (real.type !== correct.type) {
good = false;
diff = "type";
} else if (real.data !== correct.data) {
good = false;
diff = "data";
} else if (real.display !== correct.display) {
good = false;
diff = "display";
}
if (!good) {
message.pass = false;
message.message = "Difference at split " +
(i + 1) + ": " + JSON.stringify(real) +
" vs. " + JSON.stringify(correct) +
" (" + diff + " differs)";
break;
}
}
return message;
},
});
});

View File

@@ -4,6 +4,3 @@
build/**
node_modules/**
dist/**
# Third party code
test/jasmine/**

View File

@@ -16,6 +16,7 @@
"license": "MIT",
"devDependencies": {
"babel-eslint": "^7.2.0",
"babel-jest": "^20.0.3",
"babel-plugin-transform-class-properties": "^6.23.0",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-preset-es2015": "^6.18.0",
@@ -26,8 +27,7 @@
"eslint": "^3.13.0",
"express": "^4.14.0",
"glob": "^7.1.1",
"jasmine": "^2.3.2",
"jasmine-core": "^2.3.4",
"jest": "^20.0.4",
"js-yaml": "^3.3.1",
"jspngopt": "^0.2.0",
"less": "~2.7.1",
@@ -46,5 +46,13 @@
},
"dependencies": {
"match-at": "^0.1.0"
},
"jest": {
"testMatch": [
"**/test/*-spec.js"
],
"transform": {
"^.+\\.js$": "babel-jest"
}
}
}

View File

@@ -54,8 +54,6 @@ function getStatic(url, file) {
}
browserified("/katex.js", "katex", "katex");
app.use("/test/jasmine", express.static(path.dirname(
require.resolve("jasmine-core/lib/jasmine-core/jasmine.js"))));
browserified("/test/katex-spec.js", "test/*[Ss]pec.js");
browserified(
"/contrib/auto-render/auto-render.js",

View File

@@ -1,5 +1,4 @@
/* global beforeEach: false */
/* global jasmine: false */
/* global expect: false */
/* global it: false */
/* global describe: false */
@@ -10,50 +9,47 @@ import Settings from "../src/Settings";
const defaultSettings = new Settings({});
beforeEach(function() {
jasmine.addMatchers({
toFailWithParseError: function(util, customEqualityTesters) {
const prefix = "KaTeX parse error: ";
return {
compare: function(actual, expected) {
try {
parseTree(actual, defaultSettings);
return {
pass: false,
message: "'" + actual + "' parsed without error",
};
} catch (e) {
if (expected === undefined) {
return {
pass: true,
message: "'" + actual + "' parsed with error",
};
}
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 + "'",
};
}
}
},
};
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 + "'",
};
}
}
},
});
});

View File

@@ -1,11 +0,0 @@
{
"spec_dir": ".",
"spec_files": [
"test/**/*[sS]pec.js",
"contrib/**/*[sS]pec.js"
],
"helpers": [
"helpers/**/*.js",
"node_modules/babel-core/register.js"
]
}

View File

@@ -1,6 +1,5 @@
/* eslint max-len:0 */
/* global beforeEach: false */
/* global jasmine: false */
/* global expect: false */
/* global it: false */
/* global describe: false */
@@ -88,115 +87,99 @@ const parseAndSetResult = function(expr, result, settings) {
};
beforeEach(function() {
jasmine.addMatchers({
expect.extend({
toParse: function(actual, settings) {
const usedSettings = settings ? settings : defaultSettings;
toParse: function() {
return {
compare: function(actual, settings) {
const usedSettings = settings ? settings : defaultSettings;
const result = {
pass: true,
message: "'" + actual + "' succeeded parsing",
};
parseAndSetResult(actual, result, usedSettings);
return result;
},
const result = {
pass: true,
message: "'" + actual + "' succeeded parsing",
};
parseAndSetResult(actual, result, usedSettings);
return result;
},
toNotParse: function() {
return {
compare: function(actual, settings) {
const usedSettings = settings ? settings : defaultSettings;
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;
},
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() {
return {
compare: function(actual, settings) {
const usedSettings = settings ? settings : defaultSettings;
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, settings);
} 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;
},
const result = {
pass: true,
message: "'" + actual + "' succeeded in building",
};
expect(actual).toParse(usedSettings);
try {
_getBuilt(actual, settings);
} 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;
},
toParseLike: function(util, baton) {
return {
compare: function(actual, expected, settings) {
const usedSettings = settings ? settings : defaultSettings;
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 (!util.equals(actualTree, expectedTree, baton)) {
result.pass = false;
result.message = "Parse trees of '" + actual +
"' and '" + expected + "' are not equivalent";
}
return result;
},
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;
},
});
});

View File

@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<script src="jasmine/jasmine.js"></script>
<script src="jasmine/jasmine-html.js"></script>
<script src="jasmine/boot.js"></script>
<link rel="stylesheet" href="jasmine/jasmine.css">
<script src="katex-spec.js"></script>
</head>
<body>
</body>
</html>

View File

@@ -1,6 +1,5 @@
/* eslint max-len:0 */
/* global beforeEach: false */
/* global jasmine: false */
/* global expect: false */
/* global it: false */
/* global describe: false */
@@ -27,50 +26,42 @@ const parseAndSetResult = function(expr, result, settings) {
describe("unicode", function() {
beforeEach(function() {
jasmine.addMatchers({
expect.extend({
toParse: function() {
return {
compare: function(actual, settings) {
const usedSettings = settings ? settings : defaultSettings;
toParse: function(actual, settings) {
const usedSettings = settings ? settings : defaultSettings;
const result = {
pass: true,
message: "'" + actual + "' succeeded parsing",
};
parseAndSetResult(actual, result, usedSettings);
return result;
},
const result = {
pass: true,
message: "'" + actual + "' succeeded parsing",
};
parseAndSetResult(actual, result, usedSettings);
return result;
},
toNotParse: function() {
return {
compare: function(actual, settings) {
const usedSettings = settings ? settings : defaultSettings;
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;
},
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;
},
});
});