From 0fd0f026ece0cd781db39be9d244329b11cfebff Mon Sep 17 00:00:00 2001 From: Willem Date: Sun, 17 Jan 2016 21:57:59 +0100 Subject: [PATCH] Linted code and added basic tests. --- .jshintignore | 5 ++ .jshintrc | 38 ++++++++++ README.md | 11 +++ es5-ff-spa-loader.js | 168 +++++++++++++++++++++---------------------- example/example.js | 35 ++++----- package.json | 14 +++- test/jshint.spec.js | 3 + test/test-exports.js | 26 +++++++ test/test-factory.js | 26 +++++++ 9 files changed, 219 insertions(+), 107 deletions(-) create mode 100644 .jshintignore create mode 100644 .jshintrc create mode 100644 test/jshint.spec.js create mode 100644 test/test-exports.js create mode 100644 test/test-factory.js diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 0000000..c5a39d7 --- /dev/null +++ b/.jshintignore @@ -0,0 +1,5 @@ +node_modules +example/node_modules +example/www_static +example/www_views +example/mobile_app \ No newline at end of file diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..72cc948 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,38 @@ +{ + "predef": [ + "before", + "after", + "it", + "describe", + "beforeEach", + "afterEach" + ], + "asi" : false, + "bitwise" : true, + "boss" : false, + "curly" : true, + "debug": false, + "devel": false, + "eqeqeq": true, + "evil": false, + "expr": true, + "forin": false, + "immed": true, + "latedef" : false, + "laxbreak": false, + "multistr": true, + "newcap": true, + "noarg": true, + "node" : true, + "noempty": false, + "nonew": true, + "onevar": false, + "plusplus": false, + "proto": true, + "regexp": false, + "strict": false, + "sub": false, + "trailing" : true, + "undef": true, + "unused": true +} diff --git a/README.md b/README.md index b3e39cd..49ede4e 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,17 @@ A javascript library providing server defined loading of resources for a single * Iceweasel 43 * Opera 12 Presto * Android 5 in Cordova + +## Todo + + * Auto cache cleanup code. + * test in production + * Server header set+check support + * Redo css(?divs) of server question + * Add more tests + * css: set tag.media = 'only you'; + * css: add media in resouces + * Add in browser tests ## Contributing diff --git a/es5-ff-spa-loader.js b/es5-ff-spa-loader.js index 196017a..6cee125 100644 --- a/es5-ff-spa-loader.js +++ b/es5-ff-spa-loader.js @@ -1,4 +1,3 @@ -'use strict'; /* * Copyright (c) 2015-2016, Willem Cazander * All rights reserved. @@ -21,11 +20,8 @@ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - - -// TODO: -// set tag.media = 'only you'; -// add media in resouces +/* jslint browser: true */ +/* global angular,define */ /** * FFSpaLoader is an assets loader for single page applications. @@ -44,8 +40,17 @@ * * @module FFSpaLoader */ -var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { - +(function (root, factory) { + if ( typeof define === 'function' && define.amd ) { + define('FFSpaLoader', factory(root)); + } else if ( typeof exports === 'object' ) { + module.exports = factory(root); + } else { + root.FFSpaLoader = factory(root); + } +})(this || window, /** @lends module:FFSpaLoader */ function (rootWindow) { + 'use strict'; + var options = { debug: { enable: false, @@ -102,27 +107,27 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { localStorage: function() { try { var testData = 'localStorageDetect'; - localStorage.setItem(testData, testData); - localStorage.removeItem(testData); + rootWindow.localStorage.setItem(testData, testData); + rootWindow.localStorage.removeItem(testData); return true; } catch(e) { return false; } }, openDatabase: function() { - return 'openDatabase' in window; + return 'openDatabase' in rootWindow; }, sqlitePlugin: function() { - return 'sqlitePlugin' in window; + return 'sqlitePlugin' in rootWindow; }, cordova: function() { - return 'cordova' in window; + return 'cordova' in rootWindow; }, cordovaDevice: function() { - return options.boot.cordova.flag in window; + return options.boot.cordova.flag in rootWindow; }, mobileAgent: function() { - return navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|IEMobile)/); + return rootWindow.navigator !== undefined && rootWindow.navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|IEMobile)/); } }, cache: { @@ -130,7 +135,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { return { cacheGetValue: function(key, cb) { try { - var dataRaw = localStorage.getItem(key); + var dataRaw = rootWindow.localStorage.getItem(key); var data = JSON.parse(dataRaw); cb(null, data); } catch(e) { @@ -139,7 +144,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { }, cacheSetValue: function(key, value, cb) { try { - localStorage.setItem(key,JSON.stringify(value)); + rootWindow.localStorage.setItem(key,JSON.stringify(value)); cb(null); } catch(e) { cb(e); @@ -147,22 +152,22 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { }, cacheDeleteValue: function(key, cb) { try { - localStorage.removeItem(key); + rootWindow.localStorage.removeItem(key); cb(null); } catch(e) { cb(e); } } - } + }; }, websql: function(opt) { if (opt === undefined) { opt = {}; } if (opt.name === undefined) { opt.name = 'ffSpaLoader.db'; } if (opt.size === undefined) { opt.size = -1; } if (opt.version === undefined) { opt.version = '1.0'; } - if (opt.openDatabase === undefined) { opt.openDatabase = window.openDatabase; } + if (opt.openDatabase === undefined) { opt.openDatabase = rootWindow.openDatabase; } var nullDataHandler = function(cb) { - return function (tx, results) { + return function () { cb(null); }; }; @@ -175,26 +180,26 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { cacheDB.transaction(function(tx) { var query = 'CREATE TABLE cache_store(id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT NOT NULL, value TEXT NOT NULL)'; utilDebug('websql.init query '+query); - tx.executeSql(query, [], function(tx,res) { + tx.executeSql(query, [], function(tx) { var query = 'CREATE UNIQUE INDEX cache_store__key__udx ON cache_store (key)'; utilDebug('websql.init query '+query); - tx.executeSql(query, [], function(tx,res) {cb(null)}, sqlErrorHandler(cb)); + tx.executeSql(query, [], function() {cb(null);}, sqlErrorHandler(cb)); }, sqlErrorHandler(cb)); }); - } + }; return { cacheOpen: function(cb) { if (cacheDB !== null) { cb(null); // open once. return; } - cacheDB = window.openDatabase(opt.name, opt.version, opt.name, opt.size); + cacheDB = rootWindow.openDatabase(opt.name, opt.version, opt.name, opt.size); cacheDB.transaction(function(tx) { var query = 'SELECT value FROM cache_store WHERE key = \"test-for-table\"'; utilDebug('websql.cacheOpen query '+query); - tx.executeSql(query, [], function(tx,res) { + tx.executeSql(query, [], function() { cb(null); - }, function(tx,errorNoTable) { + }, function() { cacheDBInit(cb); }); }); @@ -219,13 +224,13 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { utilDebug('websql.cacheSetValue query '+query); tx.executeSql(query,[key], function(tx, res) { if (res.rows.length === 0) { - var query = 'INSERT INTO cache_store (key,value) VALUES (?,?)'; - utilDebug('websql.cacheSetValue query '+query); - tx.executeSql(query, [key,JSON.stringify(value)], nullDataHandler(cb), sqlErrorHandler(cb)); + var queryInsert = 'INSERT INTO cache_store (key,value) VALUES (?,?)'; + utilDebug('websql.cacheSetValue query '+queryInsert); + tx.executeSql(queryInsert, [key,JSON.stringify(value)], nullDataHandler(cb), sqlErrorHandler(cb)); } else { - var query = 'UPDATE cache_store SET value = ? WHERE key = ?'; - utilDebug('websql.cacheSetValue query '+query); - tx.executeSql(query, [JSON.stringify(value),key], nullDataHandler(cb), sqlErrorHandler(cb)); + var queryUpdate = 'UPDATE cache_store SET value = ? WHERE key = ?'; + utilDebug('websql.cacheSetValue query '+queryUpdate); + tx.executeSql(queryUpdate, [JSON.stringify(value),key], nullDataHandler(cb), sqlErrorHandler(cb)); } }, sqlErrorHandler(cb)); }); @@ -234,12 +239,12 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { cacheDB.transaction(function(tx) { var query = 'DELETE FROM cache_store WHERE key = ?'; utilDebug('websql.cacheDeleteValue query '+query); - tx.executeSql(query, [key], function (tx, res) { + tx.executeSql(query, [key], function () { setTimeout(function() {cb(null);}); // return next tick so transaction is flushed before location.reload }, sqlErrorHandler(cb)); }); } - } + }; }, } }; @@ -293,13 +298,13 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { var startTime = new Date().getTime(); var httpRequest = new XMLHttpRequest(); httpRequest.onreadystatechange = function() { - if (httpRequest.readyState == 4 && httpRequest.status === 200) { + if (httpRequest.readyState === 4 && httpRequest.status === 200) { utilDebug('utilHttpFetch url \"'+url+'\" done in '+(new Date().getTime()-startTime)+' ms.'); cb(null, httpRequest); - } else if (httpRequest.readyState == 4) { + } else if (httpRequest.readyState === 4) { cb('Wrong status '+httpRequest.status); } - } + }; httpRequest.open('GET', url, true); httpRequest.timeout = options.server.timeout; // ieX: after open() httpRequest.ontimeout = function() { @@ -347,12 +352,11 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { }); }; - var cleanupCache = function (resources, cb) { - utilDebug('cleanupCache TODO cacheList '+0+' resources '+resources.length); - - // TODO: impl for removes in resource lists - cb(null); - }; +// var cleanupCache = function (resources, cb) { +// utilDebug('cleanupCache TODO cacheList '+0+' resources '+resources.length); +// // TODO: impl for removes in resource lists +// cb(null); +// }; var injectResourceData = function(resource, data, cb) { utilDebug('injectResourceData resource '+JSON.stringify(resource)+' data '+data.length); @@ -387,14 +391,14 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { value.push(resource.hash); cacheSetValue('meta',cacheKey,value, cb); }); - } + }; var storeResource = function (resource, httpRequest, cb) { utilDebug('storeResource url '+resource.url+' hash '+resource.hash); var item = { resource: resource, data: httpRequest.responseText - } + }; cacheSetValue(resource.type, resource.hash, item , function(err) { if (err !== null) { cb(err); @@ -412,10 +416,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { utilDebug('loadResource '+JSON.stringify(resource)); if (cacheHasService(resource.type)) { cacheGetValue(resource.type,resource.hash,function(err, value) { - if (err !== null) { - cb(err); - return; - } + if (err !== null) { return cb(err); } if (value === null) { utilDebug('loadResource cache miss'); // + hash mismatch as its the key } else if (value.resource === undefined) { @@ -426,23 +427,18 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { return; } utilHttpFetch(resourceUrl, function(err, httpRequest) { - if (err !== null) { - cb(err); - } else { - storeResource(resource, httpRequest, function (err) { - injectResourceData(resource,httpRequest.responseText,cb); - }); - } + if (err !== null) { return cb(err); } + storeResource(resource, httpRequest, function (err) { + if (err !== null) { return cb(err); } + injectResourceData(resource,httpRequest.responseText,cb); + }); }); }); } else { // note: was links but now download + inject so order stays sequenced utilHttpFetch(resourceUrl, function(err, httpRequest) { - if (err !== null) { - cb(err); - } else { - injectResourceData(resource,httpRequest.responseText,cb); - } + if (err !== null) { return cb(err); } + injectResourceData(resource,httpRequest.responseText,cb); }); } }; @@ -452,10 +448,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { utilDebug('loadResources'); var resourceStack = resources; var resourceLoader = function(err) { - if (err !== null) { - cb(err); - return; - } + if (err !== null) { return cb(err); } resourceStack = resourceStack.slice(1); if (resourceStack.length === 0) { utilDebug('loadResources done in '+(new Date().getTime()-startTime)+' ms.'); @@ -512,7 +505,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { cb('Missing options.server.assets'); return; } - window[options.server.flag] = options.server.url; + rootWindow[options.server.flag] = options.server.url; var resourcesUrl = options.server.url + options.server.assets; utilDebug('startLoader assets \"'+resourcesUrl+'\"'); @@ -541,11 +534,8 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { utilDebug('startLoader resources '+resources.length); if (cacheHasService('meta')) { cacheSetValue('meta','server_resources',resources, function (err) { - if (err !== null) { - cb(err); - } else { - loadResources(resources, cb); - } + if (err !== null) { return cb(err); } + loadResources(resources, cb); }); } else { loadResources(resources, cb); @@ -578,25 +568,27 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { utilDebug('askUrlStart check assets '+resourcesUrl); // TODO: add+check headers - utilHttpFetch(resourcesUrl,function(err, req) { + utilHttpFetch(resourcesUrl,function(err, httpRequest) { if (err !== null) { inputErrorTag.appendChild(document.createTextNode('Error could not get data.')); return; } + if (httpRequest.responseText.length === 0) { + inputErrorTag.appendChild(document.createTextNode('Error Got empty data.')); + return; + } + document.getElementsByTagName('body')[0].removeChild(deleteTag); if (cacheHasService('meta')) { cacheSetValue('meta','server_url',options.server.url, function(err) { - if (err !== null) { - cb(err); - } else { - startLoader(cb); - } + if (err !== null) { return cb(err); } + startLoader(cb); }); } else { startLoader(cb); } - }) - } + }); + }; /** * Creates the ask url ui. @@ -666,7 +658,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { if (options.cache[type] === null) { if (factory.detect.cordovaDevice() && factory.detect.sqlitePlugin()) { utilDebug('startCacheType auto sqlitePlugin for '+type); - options.cache[type] = factory.cache.websql({openDatabase: window.sqlitePlugin}); + options.cache[type] = factory.cache.websql({openDatabase: rootWindow.sqlitePlugin}); } else if (factory.detect.openDatabase()) { utilDebug('startCacheType auto openDatabase for '+type); options.cache[type] = factory.cache.websql(); @@ -677,7 +669,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { utilDebug('startCacheType '+type+' none'); } } - if (options.cache[type] !== null && typeof options.cache[type]['cacheOpen'] === "function") { + if (options.cache[type] !== null && typeof options.cache[type].cacheOpen === 'function') { options.cache[type].cacheOpen(cb); } else { cb(null); @@ -711,12 +703,12 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { utilDebug('start done in '+(new Date().getTime()-startTime)+' ms.'); bootAngular(function(err) { if (err !== null) { return options.error.handler(err); } - if (typeof cbArgu === "function") { + if (typeof cbArgu === 'function') { cbArgu(); } }); } - } + }; if (options.debug.enable === true) { var optionsKeys = Object.keys(options); for (var keyId in optionsKeys) { @@ -759,7 +751,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { } else { cb(null); } - } + }; var bootAngular = function(cb) { if (options.boot.angular.enable !== true) { @@ -773,7 +765,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { utilDebug('bootAngular start '+options.boot.angular.modules); angular.bootstrap(document, options.boot.angular.modules); cb(null); - } + }; /** * Helper for cordova applications which want to use the sqllite plugin as cache. @@ -805,7 +797,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { bootOnce(); }, options.boot.cordova.timeout); document.addEventListener("deviceready", function () { - window[options.boot.cordova.flag] = true; + rootWindow[options.boot.cordova.flag] = true; utilDebug('bootCordova '+options.boot.cordova.flag); bootOnce(); }, false); @@ -820,4 +812,4 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { start: start, clearServerUrl: clearServerUrl }; -})(); +}); diff --git a/example/example.js b/example/example.js index 1ad0929..3404877 100644 --- a/example/example.js +++ b/example/example.js @@ -6,7 +6,7 @@ var fs = require('fs'); var minify = require('minify'); var fetch = require('fetch'); var cors = require('cors'); -var morgan = require('morgan') +var morgan = require('morgan'); // example options; var serverUrl = 'http://localhost:8080'; @@ -17,19 +17,21 @@ var clientResources = { js: [], css: [], cssData: [] -} +}; var addClientResource = function(clientResource, resourceType) { clientResources[resourceType].push(clientResource); -} +}; var stringHash = function (str) { + /* jslint bitwise: true */ var hash = 31; // prime for (var i = 0; i < str.length; i++) { hash = ((hash<<5)-hash)+str.charCodeAt(i); hash = hash & hash; // keep 32b } return hash; + /* jslint bitwise: false */ }; var fetchHashResource = function(fetchEntry,cb) { @@ -60,17 +62,17 @@ var fetchHashResources = function(fetchList, cb) { var createClientResourceFetchList = function() { var fetchList = []; - for (var clientResourceIdx in clientResources.js) { - var url = clientResources.js[clientResourceIdx]; - fetchList.push({url:url,type:'js'}); + for (var clientResourceIdxJs in clientResources.js) { + var urlJs = clientResources.js[clientResourceIdxJs]; + fetchList.push({url:urlJs,type:'js'}); } - for (var clientResourceIdx in clientResources.css) { - var url = clientResources.css[clientResourceIdx]; - fetchList.push({url:url,type:'css'}); + for (var clientResourceIdxCss in clientResources.css) { + var urlCss = clientResources.css[clientResourceIdxCss]; + fetchList.push({url:urlCss,type:'css'}); } - for (var clientResourceIdx in clientResources.cssData) { - var url = clientResources.cssData[clientResourceIdx]; - fetchList.push({url:url,type:'cssData'}); + for (var clientResourceIdxCssData in clientResources.cssData) { + var urlCssData = clientResources.cssData[clientResourceIdxCssData]; + fetchList.push({url:urlCssData,type:'cssData'}); } return fetchList; }; @@ -86,7 +88,7 @@ function renderTemplatePath(viewPath) { }; } -function renderIndex(server) { +function renderIndex() { var inline = ''; minify(__dirname+'/../es5-ff-spa-loader.js', {}, function(err, data) { inline = '\n\t\t'; @@ -113,7 +115,7 @@ addClientResource('/static/js/controller/page-foo.js','js'); addClientResource('/static/js/controller/page-index.js','js'); var server = express(); -server.use(morgan('dev')) +server.use(morgan('dev')); server.use(cors({credentials: true, origin: '*'})); server.set('view engine', 'ejs'); server.set('views', path.join(__dirname,'www_views')); @@ -133,8 +135,7 @@ server.get('/static/spa-client-resources', function (req,res) { server.get('/', function (req, res) {res.redirect('/example-ui');}); server.get('/example-ui/thtml/*', renderTemplatePath('thtml/')); -server.get('/example-ui', renderIndex(server)); -server.get('/example-ui/*', renderIndex(server)); // must be last; for HTML5 history +server.get('/example-ui', renderIndex()); console.info('Server config done.'); server.listen(8080); @@ -142,7 +143,7 @@ console.info('Server started on port 8080'); var res = createClientResourceFetchList(); fetchHashResources(res, function(err) { - if (err !== null) {console.log(err);}; + if (err !== null) {console.log(err);} console.log('Total assets build: '+clientResourcesWeb.length); }); diff --git a/package.json b/package.json index 91b2d70..68c321b 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "version": "0.0.1", "description": "Javascript Single Page Application Loader", "main": "es5-ff-spa-loader.js", + "scripts": { + "test": "export JUNIT_REPORT_PATH=test/data/report.xml;export JUNIT_REPORT_STACK=1;node_modules/mocha/bin/mocha --reporter mocha-jenkins-reporter" + }, "author": "Willem (http://forwardfire.net/)", "keywords": [ "website", @@ -14,15 +17,22 @@ "mobile", "resources", "sync", - "cache" + "cache", + "localStorage", + "openDatabase" ], "license": "BSD-2-Clause", "repository": { "type": "git", "url": "https://bitbucket.org/im_ik/es5-ff-spa-loader.git" }, + "devDependencies": { + "jsdoc": "^3.4.0", + "mocha": "2.2.x", + "mocha-jenkins-reporter": "0.1.x", + "mocha-jshint": "2.2.x" + }, "dependencies": { - "jsdoc": "^3.4.0" }, "files": [ "README.md", diff --git a/test/jshint.spec.js b/test/jshint.spec.js new file mode 100644 index 0000000..a6665ff --- /dev/null +++ b/test/jshint.spec.js @@ -0,0 +1,3 @@ +'use strict'; + +require('mocha-jshint')(['es5-ff-spa-loader.js','example']); diff --git a/test/test-exports.js b/test/test-exports.js new file mode 100644 index 0000000..2c21127 --- /dev/null +++ b/test/test-exports.js @@ -0,0 +1,26 @@ +'use strict'; + +var assert = require('assert'); +var FFSpaLoader = require('../es5-ff-spa-loader'); + +describe('Check module exports', function() { + it('FFSpaLoader.options should be defined', function() { + assert.equal(false, FFSpaLoader.options === undefined); + }); + it('FFSpaLoader.factory should be defined', function() { + assert.equal(false, FFSpaLoader.factory === undefined); + }); + it('FFSpaLoader.start should be defined', function() { + assert.equal(false, FFSpaLoader.start === undefined); + }); + it('FFSpaLoader.start should be function', function() { + assert.equal(true, typeof FFSpaLoader.start === 'function'); + }); + it('FFSpaLoader.clearServerUrl should be defined', function() { + assert.equal(false, FFSpaLoader.clearServerUrl === undefined); + }); + it('FFSpaLoader.clearServerUrl should be function', function() { + assert.equal(true, typeof FFSpaLoader.clearServerUrl === 'function'); + }); +}); + diff --git a/test/test-factory.js b/test/test-factory.js new file mode 100644 index 0000000..2242817 --- /dev/null +++ b/test/test-factory.js @@ -0,0 +1,26 @@ +'use strict'; + +var assert = require('assert'); +var FFSpaLoader = require('../es5-ff-spa-loader'); + +describe('Check factory detect', function() { + it('FFSpaLoader.factory.detect.localStorage', function() { + assert.equal(false, FFSpaLoader.factory.detect.localStorage()); + }); + it('FFSpaLoader.factory.detect.openDatabase', function() { + assert.equal(false, FFSpaLoader.factory.detect.openDatabase()); + }); + it('FFSpaLoader.factory.detect.sqlitePlugin', function() { + assert.equal(false, FFSpaLoader.factory.detect.sqlitePlugin()); + }); + it('FFSpaLoader.factory.detect.cordova', function() { + assert.equal(false, FFSpaLoader.factory.detect.cordova()); + }); + it('FFSpaLoader.factory.detect.cordovaDevice', function() { + assert.equal(false, FFSpaLoader.factory.detect.cordovaDevice()); + }); + it('FFSpaLoader.factory.detect.mobileAgent', function() { + assert.equal(false, FFSpaLoader.factory.detect.mobileAgent()); + }); +}); +