From: Chris Jaekl Date: Mon, 8 Jan 2018 08:38:15 +0000 (+0900) Subject: Adds unit test framework and a first unit test. X-Git-Url: http://jaekl.net/gitweb/?p=quanweb.git;a=commitdiff_plain;h=13efcb823cde095d5562ac061ef5a859d91c0f70;ds=sidebyside Adds unit test framework and a first unit test. --- diff --git a/js/BooksModel.js b/js/BooksModel.js deleted file mode 100644 index 75e0651..0000000 --- a/js/BooksModel.js +++ /dev/null @@ -1,96 +0,0 @@ -// ========== -// BooksModel - -var BooksModel = (function() { - - var my = {}; - - // ================= - // Private variables - - var listeners = []; - - // ================ - // Public variables - - my.cache = []; - my.count = 0, - my.first = 0, - my.ids = [], - my.last = (-1), - my.map = {}, // map from book.Id to index into cache[] - my.pageSize = 20; - - // ============== - // Public methods - - my.adjustPos = function(setting) { - - var value = parseInt(setting); - - var prev = { - first: my.first, - last: my.last, - }; - - var maxFirst = Math.max(0, my.count - my.pageSize); - - if (value < 0) { - my.first = 0; - } else if (value > maxFirst) { - my.first = maxFirst; - } else { - my.first = value; - } - - my.last = my.first + my.pageSize - 1; - if (my.last >= my.count) { - my.last = my.count - 1; - } - - if (prev.first !== my.first || prev.last !== my.last) { - my.refreshData(); - } - }; - - my.listen = function(subscriber) { - listeners.push(subscriber); - }; - - my.refreshData = function () { - var i; - var url = '/info/?ids='; - my.map = {}; - for (i = my.first; i <= my.last; ++i) { - if (i > my.first) { - url += ','; - } - var id = my.ids[i]; - url += id; - my.map[id] = i - my.first; - } - - fetch(url, {method:'GET', cache:'default'}) - .then(function(response) {return response.json();}) - .then(function(jsonValue) { - my.cache = jsonValue; - notifyAll(); // inform all subscribers that the model has been updated - }) - .catch(function(err) { - var msg = 'Error fetching book details via URL: ' + url + ': ' + err; - console.log(msg, err.stack); - report(msg); - }); - }; - - // =============== - // Private methods - - function notifyAll() { - for (var i in listeners) { - listeners[i].notify(); - } - } - - return my; -})(); diff --git a/js/BooksView.js b/js/BooksView.js deleted file mode 100644 index 1bbb8ae..0000000 --- a/js/BooksView.js +++ /dev/null @@ -1,68 +0,0 @@ -// ========= -// BooksView - -var BooksView = (function() { - var my = {}; - - // ======================== - // Private member variables - var booksModel = undefined; - - // ============== - // Public Methods - - my.init = function(linkedBooksModel) { - booksModel = linkedBooksModel; - - booksModel.listen(my); - }; - - // Called when the model changes - my.notify = function () { - var i; - var html = ''; - var limit = BooksModel.last - BooksModel.first; - for (i = 0; i <= limit; ++i) { - var book = BooksModel.cache[i]; - 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; - }; - - // =============== - // Private methods - - function bookHtml(book) { - var result = '
' - + '' - + '' - + '' - + '' - + '' - + '
'; - if (0 == book.CoverId) { - result += '(No cover available)'; - } else { - result += ''; - } - result += '' - + '

' + book.Title + '

' - + '

' - + '' + book.AuthorReading + ''; - if (typeof(book.SeriesName) !== 'undefined' && book.SeriesName.length > 0) { - result += '
' + book.SeriesName + ' ' + book.Volume + ''; - } - result += '

' - + '
' - + '
'; - return result; - } - - return my; -})(); 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/PagingController.js b/js/PagingController.js deleted file mode 100644 index c5bfb2d..0000000 --- a/js/PagingController.js +++ /dev/null @@ -1,33 +0,0 @@ -// ================ -// PagingController - -var PagingController = (function() { - var my = {}; - - var booksModel = undefined; - - // ============== - // Public Methods - - my.init = function(linkedBooksModel) { - booksModel = linkedBooksModel; - booksModel.listen(my); - }; - - my.adjustPos = function(setting) { - var value = parseInt(setting); - - if (booksModel.first === value) { - // No change - return; - } - - booksModel.adjustPos(setting); - }; - - my.notify = function() { - document.getElementById('slider').value = booksModel.first; - }; - - return my; -})(); 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/SearchController.js b/js/SearchController.js deleted file mode 100644 index b95c951..0000000 --- a/js/SearchController.js +++ /dev/null @@ -1,87 +0,0 @@ -// ================ -// SearchController - -var SearchController = (function () { - var my = {}, - booksModel = undefined; - - const terms = ['aut', 'tit', 'ser']; - - // ============== - // Public methods - - my.init = function(linkedBooksModel) { - booksModel = linkedBooksModel; - - for (var idx in terms) { - addEnterListener(terms[idx]); - } - }; - - my.onSearch = function() { - var url = constructSearchUrl(); - - fetch(url, {method:'GET', cache:'default'}) - .then(function(response) {return response.json();}) - .then(function(jsonValue) { - // console.log('JSON response: ', jsonValue); - booksModel.ids = jsonValue; - booksModel.count = booksModel.ids.length; - booksModel.first = (-1); - - var elem = document.getElementById('slider'); - elem.max = booksModel.count; - - PagingController.adjustPos(0); - }) - .catch(function(err) { - var msg = 'Error fetching JSON from URL: ' + url + ': ' + err + ':' + err.stack; - console.log(msg); - report(msg); - }); - }; - - // =============== - // Private methods - - // 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) { - event.preventDefault(); - if (event.keyCode === 13) { - document.getElementById('search').click(); - } - }); - } - - function constructSearchUrl() { - var url = window.location.protocol + '//' + window.location.host + '/search/'; - - var firstTime = true; - - for (var idx in terms) { - var term = terms[idx]; - var elem = document.getElementById(term); - if (null === elem) { - console.log('Error: could not find form element for search term "' + term + '".'); - continue; - } - - var value = elem.value; - if (value.length > 0) { - if (firstTime) { - url += '?'; - firstTime = false; - } - else { - url += '&'; - } - url += term + '=' + encodeURIComponent('%' + value + '%'); - } - } - - return url; - } - - return my; -})(); diff --git a/js/ToolTip.js b/js/ToolTip.js deleted file mode 100644 index 5e88238..0000000 --- a/js/ToolTip.js +++ /dev/null @@ -1,123 +0,0 @@ -// ======= -// ToolTip - -var ToolTip = (function () { - // ================= - // Private variables - var my = {}, - bookId = undefined, - milliSecs = 500, // time to wait before displaying tooltip - mousePos = { - x: undefined, - y: undefined - }, - threshold = 10, // number of pixels that mouse can move before tip is dismissed - timer = undefined; - - // ================ - // Public variables - my.booksModel = undefined; - - // ============== - // Public Methods - - // Set the book ID for the details pane, and then show it - my.displayDetails = function (newBookId) { - bookId = newBookId; - my.showDetails(); - }; - - // Hide the details pane, if it is currently visible - my.hideDetails = function () { - mousePos.x = undefined; - mousePos.y = undefined; - - var elem = document.getElementById('details'); - elem.innerHTML = ''; - elem.style.display = 'none'; - }; - - my.mouseMoved = function (x, y) { - // Is there an active tooltip? - if (typeof mousePos.x === 'undefined') { - // No active tooltip, so nothing further to do - return; - } - - var deltaX = Math.abs(x - mousePos.x); - var deltaY = Math.abs(y - mousePos.y); - - if ( (deltaX > threshold) - || (deltaY > threshold) ) - { - my.stopTooltipTimer(); - my.hideDetails(); - } - }; - - // Show the details pane - my.showDetails = function () { - var id = bookId; - var elem = document.getElementById('details'); - var index = BooksModel.map[id]; - var book = BooksModel.cache[index]; - var html = '

' + book.Title + '

' - + '

' + ce(book.AuthorReading) + '
' + ce(book.Series) + ' ' + ce(book.Volume) + '

' - + ce(book.Description) - + '
'; - - // Remember the current mouse (x,y). - // If we move the mouse too far from this point, that will trigger hiding the tooltip. - mousePos.x = g_state.mousePos.x; - mousePos.y = g_state.mousePos.y; - - elem.innerHTML = html; - - elem.style.display = 'block'; // show, and calculate size, so that we can query it below - - var x = mousePos.x; - var y = mousePos.y; - - var bcr = elem.getBoundingClientRect(); - - var width = bcr.width; - var height = bcr.height; - - x = Math.max(x - (width / 2), 0); - y = Math.max(y - (height / 2), 0); - - elem.style.left = x + 'px'; - elem.style.top = y + 'px'; - }; - - my.startTooltipTimer = function (newBookId) { - if (typeof timer !== 'undefined') { - clearTimeout(timer); - } - bookId = newBookId; - timer = setTimeout(my.showDetails, milliSecs); - }; - - my.stopTooltipTimer = function () { - if (typeof timer === 'undefined') { - return; - } - - clearTimeout(timer); - timer = undefined; - }; - - // =============== - // Private methods - - // ce(s): "clear if empty()" - // return s, unless it's undefined, in which case return an empty ("clear") string - function ce(s) { - if (typeof s !== 'undefined') { - return s; - } - return ''; - } - - return my; -})(); 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/src/BooksModel.js b/js/src/BooksModel.js new file mode 100644 index 0000000..1626d29 --- /dev/null +++ b/js/src/BooksModel.js @@ -0,0 +1,96 @@ +// ========== +// BooksModel + +var BooksModel = (function() { + + var my = {}; + + // ================= + // Private variables + + var listeners = []; + + // ================ + // Public variables + + my.cache = []; + 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), // last book to be displayed in the current page (0-indexed) + my.map = {}, // map from book.Id to index into cache[] + my.pageSize = 20; + + // ============== + // Public methods + + my.adjustPos = function(setting) { + + var value = parseInt(setting); + + var prev = { + first: my.first, + last: my.last, + }; + + var maxFirst = Math.max(0, my.count - my.pageSize); + + if (value < 0) { + my.first = 0; + } else if (value > maxFirst) { + my.first = maxFirst; + } else { + my.first = value; + } + + my.last = my.first + my.pageSize - 1; + if (my.last >= my.count) { + my.last = my.count - 1; + } + + if (prev.first !== my.first || prev.last !== my.last) { + my.refreshData(); + } + }; + + my.listen = function(subscriber) { + listeners.push(subscriber); + }; + + my.refreshData = function () { + var i; + var url = '/info/?ids='; + my.map = {}; + for (i = my.first; i <= my.last; ++i) { + if (i > my.first) { + url += ','; + } + var id = my.ids[i]; + url += id; + my.map[id] = i - my.first; + } + + fetch(url, {method:'GET', cache:'default'}) + .then(function(response) {return response.json();}) + .then(function(jsonValue) { + my.cache = jsonValue; + notifyAll(); // inform all subscribers that the model has been updated + }) + .catch(function(err) { + var msg = 'Error fetching book details via URL: ' + url + ': ' + err; + console.log(msg, err.stack); + report(msg); + }); + }; + + // =============== + // Private methods + + function notifyAll() { + for (var i in listeners) { + listeners[i].notify(); + } + } + + return my; +})(); diff --git a/js/src/BooksView.js b/js/src/BooksView.js new file mode 100644 index 0000000..0259edd --- /dev/null +++ b/js/src/BooksView.js @@ -0,0 +1,68 @@ +// ========= +// BooksView + +var BooksView = (function() { + var my = {}; + + // ======================== + // Private member variables + var booksModel = undefined; + + // ============== + // Public Methods + + my.init = function(linkedBooksModel) { + booksModel = linkedBooksModel; + + booksModel.listen(my); + }; + + // Called when the model changes + my.notify = function () { + var i; + var html = ''; + var limit = BooksModel.last - BooksModel.first; + for (i = 0; i <= limit; ++i) { + var book = BooksModel.cache[i]; + html += bookHtml(book); + } + + 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; + }; + + // =============== + // Private methods + + function bookHtml(book) { + var result = '
' + + '' + + '' + + '' + + '' + + '' + + '
'; + if (0 == book.CoverId) { + result += '(No cover available)'; + } else { + result += ''; + } + result += '' + + '

' + book.Title + '

' + + '

' + + '' + book.AuthorReading + ''; + if (typeof(book.SeriesName) !== 'undefined' && book.SeriesName.length > 0) { + result += '
' + book.SeriesName + ' ' + book.Volume + ''; + } + result += '

' + + '
' + + '
'; + return result; + } + + return my; +})(); diff --git a/js/src/PagingController.js b/js/src/PagingController.js new file mode 100644 index 0000000..c043dc1 --- /dev/null +++ b/js/src/PagingController.js @@ -0,0 +1,33 @@ +// ================ +// PagingController + +var PagingController = (function() { + var my = {}; + + var booksModel = undefined; + + // ============== + // Public Methods + + my.init = function(linkedBooksModel) { + booksModel = linkedBooksModel; + booksModel.listen(my); + }; + + my.adjustPos = function(setting) { + var value = parseInt(setting); + + if (booksModel.first === value) { + // No change + return; + } + + booksModel.adjustPos(setting); + }; + + my.notify = function() { + Browser.getElementById('slider').value = booksModel.first; + }; + + return my; +})(); diff --git a/js/src/SearchController.js b/js/src/SearchController.js new file mode 100644 index 0000000..0f13194 --- /dev/null +++ b/js/src/SearchController.js @@ -0,0 +1,87 @@ +// ================ +// SearchController + +var SearchController = (function () { + var my = {}, + booksModel = undefined; + + const terms = ['aut', 'tit', 'ser']; + + // ============== + // Public methods + + my.init = function(linkedBooksModel) { + booksModel = linkedBooksModel; + + for (var idx in terms) { + addEnterListener(terms[idx]); + } + }; + + my.onSearch = function() { + var url = constructSearchUrl(); + + fetch(url, {method:'GET', cache:'default'}) + .then(function(response) {return response.json();}) + .then(function(jsonValue) { + // console.log('JSON response: ', jsonValue); + booksModel.ids = jsonValue; + booksModel.count = booksModel.ids.length; + booksModel.first = (-1); + + var elem = Browser.getElementById('slider'); + elem.max = booksModel.count; + + PagingController.adjustPos(0); + }) + .catch(function(err) { + var msg = 'Error fetching JSON from URL: ' + url + ': ' + err + ':' + err.stack; + console.log(msg); + report(msg); + }); + }; + + // =============== + // Private methods + + // KeyUp listener. If the key is [Enter], then trigger a click on the [Search] button. + function addEnterListener(ctrlId) { + Browser.getElementById(ctrlId).addEventListener('keyup', function(event) { + event.preventDefault(); + if (event.keyCode === 13) { + Browser.getElementById('search').click(); + } + }); + } + + function constructSearchUrl() { + var url = window.location.protocol + '//' + window.location.host + '/search/'; + + var firstTime = true; + + for (var idx in terms) { + var term = terms[idx]; + var elem = Browser.getElementById(term); + if (null === elem) { + console.log('Error: could not find form element for search term "' + term + '".'); + continue; + } + + var value = elem.value; + if (value.length > 0) { + if (firstTime) { + url += '?'; + firstTime = false; + } + else { + url += '&'; + } + url += term + '=' + encodeURIComponent('%' + value + '%'); + } + } + + return url; + } + + return my; +})(); diff --git a/js/src/ToolTip.js b/js/src/ToolTip.js new file mode 100644 index 0000000..e622e2a --- /dev/null +++ b/js/src/ToolTip.js @@ -0,0 +1,123 @@ +// ======= +// ToolTip + +var ToolTip = (function () { + // ================= + // Private variables + var my = {}, + bookId = undefined, + milliSecs = 500, // time to wait before displaying tooltip + mousePos = { + x: undefined, + y: undefined + }, + threshold = 10, // number of pixels that mouse can move before tip is dismissed + timer = undefined; + + // ================ + // Public variables + my.booksModel = undefined; + + // ============== + // Public Methods + + // Set the book ID for the details pane, and then show it + my.displayDetails = function (newBookId) { + bookId = newBookId; + my.showDetails(); + }; + + // Hide the details pane, if it is currently visible + my.hideDetails = function () { + mousePos.x = undefined; + mousePos.y = undefined; + + var elem = Browser.getElementById('details'); + elem.innerHTML = ''; + elem.style.display = 'none'; + }; + + my.mouseMoved = function (x, y) { + // Is there an active tooltip? + if (typeof mousePos.x === 'undefined') { + // No active tooltip, so nothing further to do + return; + } + + var deltaX = Math.abs(x - mousePos.x); + var deltaY = Math.abs(y - mousePos.y); + + if ( (deltaX > threshold) + || (deltaY > threshold) ) + { + my.stopTooltipTimer(); + my.hideDetails(); + } + }; + + // Show the details pane + my.showDetails = function () { + var id = bookId; + var elem = Browser.getElementById('details'); + var index = BooksModel.map[id]; + var book = BooksModel.cache[index]; + var html = '

' + book.Title + '

' + + '

' + ce(book.AuthorReading) + '
' + ce(book.Series) + ' ' + ce(book.Volume) + '

' + + ce(book.Description) + + '
'; + + // Remember the current mouse (x,y). + // If we move the mouse too far from this point, that will trigger hiding the tooltip. + mousePos.x = g_state.mousePos.x; + mousePos.y = g_state.mousePos.y; + + elem.innerHTML = html; + + elem.style.display = 'block'; // show, and calculate size, so that we can query it below + + var x = mousePos.x; + var y = mousePos.y; + + var bcr = elem.getBoundingClientRect(); + + var width = bcr.width; + var height = bcr.height; + + x = Math.max(x - (width / 2), 0); + y = Math.max(y - (height / 2), 0); + + elem.style.left = x + 'px'; + elem.style.top = y + 'px'; + }; + + my.startTooltipTimer = function (newBookId) { + if (typeof timer !== 'undefined') { + clearTimeout(timer); + } + bookId = newBookId; + timer = setTimeout(my.showDetails, milliSecs); + }; + + my.stopTooltipTimer = function () { + if (typeof timer === 'undefined') { + return; + } + + clearTimeout(timer); + timer = undefined; + }; + + // =============== + // Private methods + + // ce(s): "clear if empty()" + // return s, unless it's undefined, in which case return an empty ("clear") string + function ce(s) { + if (typeof s !== 'undefined') { + return s; + } + return ''; + } + + return my; +})(); 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]); + } + }); +}); + +