diff --git a/openlibrary/plugins/openlibrary/js/index.js b/openlibrary/plugins/openlibrary/js/index.js index b9445ccc07f0..ce105dd33dba 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 65ef14b73954..4764c5c31d95 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 58458bdd3199..e1dd10379c5e 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 dea3a715bb5e..e519ed59ce03 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 000000000000..ffd805fdd463 --- /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 000000000000..d6631688b51c --- /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 (val, i) { + 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('