From 13efcb823cde095d5562ac061ef5a859d91c0f70 Mon Sep 17 00:00:00 2001 From: Chris Jaekl Date: Mon, 8 Jan 2018 17:38:15 +0900 Subject: [PATCH] Adds unit test framework and a first unit test. --- js/Gruntfile.js | 51 ++++++++++++++--------- js/Main.js | 4 +- js/README | 8 ++++ js/karma.conf.js | 69 ++++++++++++++++++++++++++++++++ js/package.json | 14 ++++++- js/{ => src}/BooksModel.js | 8 ++-- js/{ => src}/BooksView.js | 8 ++-- js/{ => src}/PagingController.js | 2 +- js/{ => src}/SearchController.js | 8 ++-- js/{ => src}/ToolTip.js | 4 +- js/test/BooksModelTest.js | 49 +++++++++++++++++++++++ 11 files changed, 188 insertions(+), 37 deletions(-) create mode 100644 js/README create mode 100644 js/karma.conf.js rename js/{ => src}/BooksModel.js (87%) rename js/{ => src}/BooksView.js (87%) rename js/{ => src}/PagingController.js (89%) rename js/{ => src}/SearchController.js (89%) rename js/{ => src}/ToolTip.js (96%) create mode 100644 js/test/BooksModelTest.js diff --git a/js/Gruntfile.js b/js/Gruntfile.js index c6ebaa9..f6fee4d 100644 --- a/js/Gruntfile.js +++ b/js/Gruntfile.js @@ -1,25 +1,38 @@ module.exports = function(grunt) { - // Project configuration. - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - uglify: { - options: { - banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n', - mangle: false - }, - app: { - files: { - '../app/lib.min.js': ['BooksModel.js', 'BooksView.js', 'PagingController.js', 'SearchController.js', 'ToolTip.js', 'Main.js'] - } - } - } - }); + // Project configuration. + grunt.initConfig({ + concat: { + all: { + src: ['src/**/*.js', 'Main.js'], + dest: '../app/all.js', + nonull: true + }, + tests: { + src: ['src/**/*.js', 'test/**/*.js'], + dest: 'all_tests.js', + nonull: true + }, + }, + + pkg: grunt.file.readJSON('package.json'), - // Load the plugin that provides the "uglify" task. - grunt.loadNpmTasks('grunt-contrib-uglify'); + uglify: { + options: { + banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n', + mangle: false + }, + app: { + files: { + '../app/lib.min.js': ['src/**/*.js', 'Main.js'] + } + } + } + }); - // Default task(s). - grunt.registerTask('default', ['uglify']); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + // Default task(s). + grunt.registerTask('default', ['concat', 'uglify']); }; diff --git a/js/Main.js b/js/Main.js index 5f253bc..22e7dd6 100644 --- a/js/Main.js +++ b/js/Main.js @@ -14,7 +14,7 @@ var g_state = { // ============== // Initialization -document.onmousemove = onMouseMove; +Browser.setOnMouseMove(onMouseMove); BooksView.init(BooksModel); PagingController.init(BooksModel); @@ -36,7 +36,7 @@ else { // TODO: refactor this to compartmentalize more functionality. function report(message) { - document.getElementById('books').innerHTML = message; + Browser.getElementById('books').innerHTML = message; } function onMouseMove(event) { diff --git a/js/README b/js/README new file mode 100644 index 0000000..7f8b60c --- /dev/null +++ b/js/README @@ -0,0 +1,8 @@ +QuanWeb +Web service to provide accces to a QuanLib database of e-books + +On the off chance that you want this code, you're welcome to it, subject to the +GNU General Public Licence version 3 (or, at your option, any later version). + +Chris Jaekl +cejaekl@yahoo.com diff --git a/js/karma.conf.js b/js/karma.conf.js new file mode 100644 index 0000000..f0d3c21 --- /dev/null +++ b/js/karma.conf.js @@ -0,0 +1,69 @@ +// Karma configuration +// Generated on Sun Dec 31 2017 15:08:47 GMT+0900 (JST) + +module.exports = function(config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '', + + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine'], + + + // list of files / patterns to load in the browser + files: [ + 'all_tests.js' + ], + + + // list of files / patterns to exclude + exclude: [ + ], + + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + }, + + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress'], + + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['Chrome'], + + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false, + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity + }) +} diff --git a/js/package.json b/js/package.json index 2cf9e61..3836a14 100644 --- a/js/package.json +++ b/js/package.json @@ -1,11 +1,23 @@ { "name": "quanweb", "version": "0.0.1", + "description": "E-Book Library Web User Interface", "license": "GPL-3.0+", "devDependencies": { "grunt": "^1.0.1", + "grunt-contrib-concat": "^1.0.1", "grunt-contrib-jshint": "~0.10.0", "grunt-contrib-nodeunit": "~0.4.1", - "grunt-contrib-uglify": "git://github.com/gruntjs/grunt-contrib-uglify.git#harmony" + "grunt-contrib-uglify": "git://github.com/gruntjs/grunt-contrib-uglify.git#harmony", + "grunt-mocha-test": "^0.13.3", + "jasmine": "^2.8.0", + "jasmine-core": "^2.8.0", + "karma": "^2.0.0", + "karma-chrome-launcher": "^2.2.0", + "karma-jasmine": "^1.1.1", + "mocha": "^4.0.1", + }, + "scripts": { + "test": "./node_modules/karma/bin/karma start karma.conf.js" } } diff --git a/js/BooksModel.js b/js/src/BooksModel.js similarity index 87% rename from js/BooksModel.js rename to js/src/BooksModel.js index 75e0651..1626d29 100644 --- a/js/BooksModel.js +++ b/js/src/BooksModel.js @@ -14,11 +14,11 @@ var BooksModel = (function() { // Public variables my.cache = []; - my.count = 0, - my.first = 0, + my.count = 0, // number of books available to be paged through + my.first = 0, // first book to be displayed in current page (0-indexed) my.ids = [], - my.last = (-1), - my.map = {}, // map from book.Id to index into cache[] + my.last = (-1), // last book to be displayed in the current page (0-indexed) + my.map = {}, // map from book.Id to index into cache[] my.pageSize = 20; // ============== diff --git a/js/BooksView.js b/js/src/BooksView.js similarity index 87% rename from js/BooksView.js rename to js/src/BooksView.js index 1bbb8ae..0259edd 100644 --- a/js/BooksView.js +++ b/js/src/BooksView.js @@ -27,10 +27,10 @@ var BooksView = (function() { html += bookHtml(book); } - document.getElementById('books').innerHTML = html; - document.getElementById('first').innerHTML = (BooksModel.first + 1); - document.getElementById('last').innerHTML = (BooksModel.last + 1); - document.getElementById('count').innerHTML = BooksModel.count; + Browser.getElementById('books').innerHTML = html; + Browser.getElementById('first').innerHTML = (BooksModel.first + 1); + Browser.getElementById('last').innerHTML = (BooksModel.last + 1); + Browser.getElementById('count').innerHTML = BooksModel.count; }; // =============== diff --git a/js/PagingController.js b/js/src/PagingController.js similarity index 89% rename from js/PagingController.js rename to js/src/PagingController.js index c5bfb2d..c043dc1 100644 --- a/js/PagingController.js +++ b/js/src/PagingController.js @@ -26,7 +26,7 @@ var PagingController = (function() { }; my.notify = function() { - document.getElementById('slider').value = booksModel.first; + Browser.getElementById('slider').value = booksModel.first; }; return my; diff --git a/js/SearchController.js b/js/src/SearchController.js similarity index 89% rename from js/SearchController.js rename to js/src/SearchController.js index b95c951..0f13194 100644 --- a/js/SearchController.js +++ b/js/src/SearchController.js @@ -29,7 +29,7 @@ var SearchController = (function () { booksModel.count = booksModel.ids.length; booksModel.first = (-1); - var elem = document.getElementById('slider'); + var elem = Browser.getElementById('slider'); elem.max = booksModel.count; PagingController.adjustPos(0); @@ -46,10 +46,10 @@ var SearchController = (function () { // KeyUp listener. If the key is [Enter], then trigger a click on the [Search] button. function addEnterListener(ctrlId) { - document.getElementById(ctrlId).addEventListener('keyup', function(event) { + Browser.getElementById(ctrlId).addEventListener('keyup', function(event) { event.preventDefault(); if (event.keyCode === 13) { - document.getElementById('search').click(); + Browser.getElementById('search').click(); } }); } @@ -61,7 +61,7 @@ var SearchController = (function () { for (var idx in terms) { var term = terms[idx]; - var elem = document.getElementById(term); + var elem = Browser.getElementById(term); if (null === elem) { console.log('Error: could not find form element for search term "' + term + '".'); continue; diff --git a/js/ToolTip.js b/js/src/ToolTip.js similarity index 96% rename from js/ToolTip.js rename to js/src/ToolTip.js index 5e88238..e622e2a 100644 --- a/js/ToolTip.js +++ b/js/src/ToolTip.js @@ -32,7 +32,7 @@ var ToolTip = (function () { mousePos.x = undefined; mousePos.y = undefined; - var elem = document.getElementById('details'); + var elem = Browser.getElementById('details'); elem.innerHTML = ''; elem.style.display = 'none'; }; @@ -58,7 +58,7 @@ var ToolTip = (function () { // Show the details pane my.showDetails = function () { var id = bookId; - var elem = document.getElementById('details'); + var elem = Browser.getElementById('details'); var index = BooksModel.map[id]; var book = BooksModel.cache[index]; var html = '

' + book.Title + '

' diff --git a/js/test/BooksModelTest.js b/js/test/BooksModelTest.js new file mode 100644 index 0000000..38997e4 --- /dev/null +++ b/js/test/BooksModelTest.js @@ -0,0 +1,49 @@ +'use strict'; + +describe('Scroll forward should not advance beyond end', function() { + var bm = BooksModel; + var calledRefreshData; + + // [first, last, pageSize, count, setting, expectFirst, expectLast, expectRefresh] + data = [ [ 0, 19 , 20, 100, 20, 20 , 39 , true ], + [ 0, 19 , 20, 100, 100, 80 , 99 , true ], + [ 0, 19 , 20, 100, 80, 80 , 99 , true ], + [ 0, 19 , 20, 100, 79, 79 , 98 , true ], + [ 79, 99 , 20, 100, 50, 50 , 69 , true ], + [ 0, 11 , 20, 12, 19, 0 , 11 , false ], + [ 0, (-1), 20, 0, 20, 0 , (-1), false ] + ]; + + function doTest(datum) { + bm.first = datum[0]; + bm.last = datum[1]; + bm.pageSize = datum[2]; + bm.count = datum[3]; + + var oldFunc = bm.refreshData; + calledRefreshData = false; + try { + bm.refreshData = function() { + calledRefreshData = true; + }; + + bm.adjustPos(datum[4]); + } + finally { + bm.refreshData = oldFunc; + } + } + + it('should stay within valid range, and call refreshData() if bm.first changes', function() { + var i = 4; + for (i = 0; i < data.length; i += 1) { + datum = data[i]; + doTest(datum); + expect(bm.first).toBe(datum[5]); + expect(bm.last).toBe(datum[6]); + expect(calledRefreshData).toBe(datum[7]); + } + }); +}); + + -- 2.39.2