From 398916d1ae5e2b8edc4001a7c91a503422634cc6 Mon Sep 17 00:00:00 2001 From: jdlrobson Date: Fri, 22 Mar 2019 17:01:11 -0700 Subject: [PATCH] Add unit testing via the Jest framework From now on we'll write unit tests to strengthen our code. I begin by writing tests for two simple files. In the process, I discover a bug in htmlquote (which I fix!) Code coverage is reported and lowest coverage possible is defined in package.json. We will work towards increasing this with time as we refactor. For now code coverage is 1%.. you have to start somewhere!! Fixes: #1999 --- openlibrary/plugins/openlibrary/js/index.js | 3 +- openlibrary/plugins/openlibrary/js/jsdef.js | 10 ++-- openlibrary/plugins/openlibrary/js/python.js | 8 +++ package.json | 16 ++++++ tests/unit/.eslintrc.json | 10 ++++ tests/unit/js/test.jsdef.js | 57 ++++++++++++++++++++ tests/unit/js/test.python.js | 9 ++++ 7 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 tests/unit/.eslintrc.json create mode 100644 tests/unit/js/test.jsdef.js create mode 100644 tests/unit/js/test.python.js diff --git a/openlibrary/plugins/openlibrary/js/index.js b/openlibrary/plugins/openlibrary/js/index.js index b9445ccc07f..ce105dd33db 100644 --- a/openlibrary/plugins/openlibrary/js/index.js +++ b/openlibrary/plugins/openlibrary/js/index.js @@ -10,7 +10,7 @@ import loadStyle from './loadStyle'; import { ungettext, ugettext, sprintf } from './i18n'; import { addFadeInFunctionsTojQuery } from './jquery.others'; import jQueryRepeat from './jquery.repeat'; -import { ForLoop, enumerate, htmlquote, websafe, foreach, join, len, range } from './jsdef'; +import { enumerate, htmlquote, websafe, foreach, join, len, range } from './jsdef'; // Note this import will also load various jQuery plugins. // (jQuery.ScrollTo, jquery.hoverIntent, jquery.dataTables, dataTableExt, // highlight, removeHighlight, jTruncate, columnize) @@ -59,7 +59,6 @@ window._uggettext = ugettext; window.Browser = Browser; window.Carousel = Carousel; -window.ForLoop = ForLoop; window.Subject = Subject; window.Template = Template; diff --git a/openlibrary/plugins/openlibrary/js/jsdef.js b/openlibrary/plugins/openlibrary/js/jsdef.js index 65ef14b7395..4764c5c31d9 100644 --- a/openlibrary/plugins/openlibrary/js/jsdef.js +++ b/openlibrary/plugins/openlibrary/js/jsdef.js @@ -131,10 +131,10 @@ export function websafe(value) { export function htmlquote(text) { // This code exists for compatibility with template.js text = String(text); - text = text.replace("&", "&"); // Must be done first! - text = text.replace("<", "<"); - text = text.replace(">", ">"); - text = text.replace("'", "'"); - text = text.replace('"', """); + text = text.replace(/&/g, "&"); // Must be done first! + text = text.replace(//g, ">"); + text = text.replace(/'/g, "'"); + text = text.replace(/"/g, """); return text; } diff --git a/openlibrary/plugins/openlibrary/js/python.js b/openlibrary/plugins/openlibrary/js/python.js index 58458bdd319..e1dd10379c5 100644 --- a/openlibrary/plugins/openlibrary/js/python.js +++ b/openlibrary/plugins/openlibrary/js/python.js @@ -1,4 +1,12 @@ // used in templates/admin/index.html + +/** + * Add commas to a number + * e.g. 1000 becomes 1,000 + * 1 million becomes 1,000,000 + * @param {mixed} n + * @return {string} + */ export function commify(n) { var text = n.toString(); var re = /(\d+)(\d{3})/; diff --git a/package.json b/package.json index dea3a715bb5..e519ed59ce0 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "svg-min": "svgo static/images/**/*.svg && svgo static/images/*.svg", "lint:fix": "stylelint --syntax less --fix static/css/", "lint:js": "eslint --config .eslintrc.json .", + "test:unit": "jest --testRegex tests/unit/**/*.js", "test": "npm run check-bundles && stylelint --syntax less static/css/ && npm run lint:js" }, "bundlesize": [ @@ -62,6 +63,7 @@ "bundlesize": "^0.17.0", "detect-libc": "^1.0.3", "eslint": "^5.7.0", + "jest": "^24.5.0", "less": "^3.8.1", "node-gyp": "^3.8.0", "prebuild-install": "^5.2.1", @@ -70,5 +72,19 @@ "svgo": "1.1.1", "webpack": "4.27.1", "webpack-cli": "3.1.2" + }, + "jest": { + "collectCoverageFrom": [ + "openlibrary/plugins/openlibrary/js/**/*.js" + ], + "coverageThreshold": { + "global": { + "branches": 0.6, + "functions": 1, + "lines": 1, + "statements": 1 + } + }, + "collectCoverage": true } } diff --git a/tests/unit/.eslintrc.json b/tests/unit/.eslintrc.json new file mode 100644 index 00000000000..ffd805fdd46 --- /dev/null +++ b/tests/unit/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "env": { + "es6": true, + "jest": true + }, + "globals": { + "test": true, + "expect": true + } +} diff --git a/tests/unit/js/test.jsdef.js b/tests/unit/js/test.jsdef.js new file mode 100644 index 00000000000..f1f69ed93ae --- /dev/null +++ b/tests/unit/js/test.jsdef.js @@ -0,0 +1,57 @@ +import { foreach, range, join, len, htmlquote, enumerate, + websafe } from '../../../openlibrary/plugins/openlibrary/js/jsdef'; + +test('jsdef: python range function', () => { + expect(range(2, 5)).toEqual([2, 3, 4]); + expect(range(5)).toEqual([0, 1, 2, 3, 4]); + expect(range(0, 10, 2)).toEqual([0, 2, 4, 6, 8]); +}); + +test('jsdef: enumerate', () => { + expect(enumerate([1, 2, 3])).toEqual([ + ['0', 1], + ['1', 2], + ['2', 3] + ]); +}); + +test('jsdef: foreach', () => { + let called = 0; + const loop = []; + const listToLoop = [1, 2, 3]; + expect.assertions(1); + return new Promise((resolve) => { + foreach(listToLoop, loop, function () { + called += 1; + if (called === 3) { + expect(called).toBe(3); + resolve(); + } + }) + }); +}); + +test('jsdef: join', () => { + const str = '-'; + const joinFn = join.bind(str); + expect(joinFn(['1', '2'])).toBe('1-2'); +}); + +test('jsdef: len', () => { + expect(len(['1', '2'])).toBe(2); +}); + +test('jsdef: htmlquote', () => { + expect(htmlquote(5)).toBe('5'); + expect(htmlquote('')).toBe('<foo>'); + expect(htmlquote('\'foo\': "bar"')).toBe(''foo': "bar"'); + expect(htmlquote('a&b')).toBe('a&b'); +}); + +test('jsdef: websafe', () => { + expect(websafe('