diff --git a/.gitignore b/.gitignore index 1c6a7879..484c68f9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ diff.png /test/symgroups.log /test/symgroups.pdf /test/screenshotter/unicode-fonts +coverage/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a8981f6d..16f771cd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 diff --git a/Makefile b/Makefile index e52cfdb7..b0edeb7b 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/contrib/auto-render/auto-render-spec.js b/contrib/auto-render/auto-render-spec.js index 6fc589b4..31e9deae 100644 --- a/contrib/auto-render/auto-render-spec.js +++ b/contrib/auto-render/auto-render-spec.js @@ -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; }, }); }); diff --git a/lint_blacklist.txt b/lint_blacklist.txt index 26d9dab8..688aebdb 100644 --- a/lint_blacklist.txt +++ b/lint_blacklist.txt @@ -4,6 +4,3 @@ build/** node_modules/** dist/** - -# Third party code -test/jasmine/** diff --git a/package.json b/package.json index 89af860b..cabc1c27 100644 --- a/package.json +++ b/package.json @@ -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" + } } } diff --git a/server.js b/server.js index 193df9a1..0a1244f0 100644 --- a/server.js +++ b/server.js @@ -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", diff --git a/test/errors-spec.js b/test/errors-spec.js index a2853464..7e2fc22f 100644 --- a/test/errors-spec.js +++ b/test/errors-spec.js @@ -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 + "'", + }; + } + } }, }); }); diff --git a/test/jasmine.json b/test/jasmine.json deleted file mode 100644 index 6b96cef7..00000000 --- a/test/jasmine.json +++ /dev/null @@ -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" - ] -} diff --git a/test/katex-spec.js b/test/katex-spec.js index 7b218727..7b2238d2 100644 --- a/test/katex-spec.js +++ b/test/katex-spec.js @@ -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; + }, }); }); diff --git a/test/test.html b/test/test.html deleted file mode 100644 index 713c5672..00000000 --- a/test/test.html +++ /dev/null @@ -1,12 +0,0 @@ - - -
- - - - - - - - - diff --git a/test/unicode-spec.js b/test/unicode-spec.js index 3ad35802..dca0b873 100644 --- a/test/unicode-spec.js +++ b/test/unicode-spec.js @@ -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; }, }); });