diff --git a/.eslintignore b/.eslintignore index 03c66a54..b3a47729 100644 --- a/.eslintignore +++ b/.eslintignore @@ -5,3 +5,4 @@ dist installer node_modules js/preload/_*.js +tests/** diff --git a/js/badgeCount.js b/js/badgeCount.js index 6682435a..0ee4264d 100644 --- a/js/badgeCount.js +++ b/js/badgeCount.js @@ -4,7 +4,7 @@ const electron = require('electron'); const app = electron.app; const nativeImage = electron.nativeImage; -const { isMac } = require('./utils.js'); +const { isMac } = require('./utils/misc.js'); const windowMgr = require('./windowMgr.js'); const maxCount = 1e8; diff --git a/js/getConfig.js b/js/getConfig.js index e7499558..8c29480d 100644 --- a/js/getConfig.js +++ b/js/getConfig.js @@ -4,8 +4,8 @@ const electron = require('electron'); const app = electron.app; const path = require('path'); const fs = require('fs'); -const isDevEnv = require('./utils.js').isDevEnv; -const isMac = require('./utils.js').isMac; +const isDevEnv = require('./utils/misc.js').isDevEnv; +const isMac = require('./utils/misc.js').isMac; /** * reads global configuration file: config/Symphony.config. this file is diff --git a/js/main.js b/js/main.js index a0eb6c77..cb92d5f0 100644 --- a/js/main.js +++ b/js/main.js @@ -6,7 +6,7 @@ const nodeURL = require('url'); const squirrelStartup = require('electron-squirrel-startup'); const getConfig = require('./getConfig.js'); -const { isMac } = require('./utils.js'); +const { isMac } = require('./utils/misc.js'); // exit early for squirrel installer if (squirrelStartup) { diff --git a/js/preload/preloadMain.js b/js/preload/preloadMain.js index 4a1d9a47..df6ffb71 100644 --- a/js/preload/preloadMain.js +++ b/js/preload/preloadMain.js @@ -14,6 +14,7 @@ const { ipcRenderer } = require('electron'); +const throttle = require('../utils/throttle.js'); const apiEnums = require('../enums/api.js'); const apiCmds = apiEnums.cmds; const apiName = apiEnums.apiName; @@ -23,6 +24,14 @@ const local = { ipcRenderer: ipcRenderer }; +// throttle calls to this func to at most once per sec, called on leading edge. +const throttledSetBadgeCount = throttle(1000, function(count) { + local.ipcRenderer.send(apiName, { + cmd: apiCmds.setBadgeCount, + count: count + }); +}); + // // API exposed to renderer main window process. // @@ -41,15 +50,11 @@ window.SYM_API = { * sets the count on the tray icon to the given number. * @param {number} count count to be displayed * note: count of 0 will remove the displayed count. - * note: for mac the number displayed will be 0 to 49 and 50+ - * note: for windws the number displayed will be 0 to 9 and 10+ (since imgs - * are used here). + * note: for mac the number displayed will be 1 to infinity + * note: for windws the number displayed will be 1 to 99 and 99+ */ setBadgeCount: function(count) { - local.ipcRenderer.send(apiName, { - cmd: apiCmds.setBadgeCount, - count: count - }); + throttledSetBadgeCount(count); }, /** diff --git a/js/utils.js b/js/utils/getGuid.js similarity index 62% rename from js/utils.js rename to js/utils/getGuid.js index 0b9179d9..15173238 100644 --- a/js/utils.js +++ b/js/utils/getGuid.js @@ -1,10 +1,5 @@ 'use strict'; -const isDevEnv = process.env.ELECTRON_DEV ? - process.env.ELECTRON_DEV.trim().toLowerCase() === 'true' : false; - -const isMac = (process.platform === 'darwin'); - /** * Generates a guid, * http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript @@ -20,8 +15,4 @@ function getGuid() { return guid; } -module.exports = { - isDevEnv: isDevEnv, - isMac: isMac, - getGuid: getGuid -}; +module.exports = getGuid; diff --git a/js/utils/misc.js b/js/utils/misc.js new file mode 100644 index 00000000..8f0f0b75 --- /dev/null +++ b/js/utils/misc.js @@ -0,0 +1,11 @@ +'use strict'; + +const isDevEnv = process.env.ELECTRON_DEV ? + process.env.ELECTRON_DEV.trim().toLowerCase() === 'true' : false; + +const isMac = (process.platform === 'darwin'); + +module.exports = { + isDevEnv: isDevEnv, + isMac: isMac +}; diff --git a/js/utils/throttle.js b/js/utils/throttle.js new file mode 100644 index 00000000..a91c71d7 --- /dev/null +++ b/js/utils/throttle.js @@ -0,0 +1,38 @@ +'use strict'; + +/** + * throttles calls to given function at most once a second. + * @param {number} throttleTime minimum time between calls + * @param {function} func function to invoke + */ +function throttle(throttleTime, func) { + let timer, lastInvoke = 0; + return function() { + let args = arguments; + + function invoke(argsToInvoke) { + timer = null; + lastInvoke = Date.now(); + func.apply(null, argsToInvoke); + } + + function cancel() { + if (timer) { + window.clearTimeout(timer); + } + } + + let now = Date.now(); + if (now - lastInvoke < throttleTime) { + cancel(); + timer = setTimeout(function() { + invoke(args); + }, lastInvoke + throttleTime - now); + } else { + cancel(); + invoke(args); + } + } +} + +module.exports = throttle; diff --git a/js/windowMgr.js b/js/windowMgr.js index ac245241..2d1b49a6 100644 --- a/js/windowMgr.js +++ b/js/windowMgr.js @@ -6,7 +6,8 @@ const path = require('path'); const menuTemplate = require('./menuTemplate.js'); const loadErrors = require('./dialogs/showLoadError.js'); -const { isMac, getGuid } = require('./utils.js'); +const { isMac } = require('./utils/misc.js'); +const getGuid = require('./utils/getGuid.js'); const log = require('./log.js') const logLevels = require('./enums/logLevels.js'); @@ -70,8 +71,8 @@ function createMainWindow(url) { }); mainWindow.webContents.on('did-fail-load', function(event, errorCode, - errorDesc) { - loadErrors.showLoadFailure(mainWindow, url, errorDesc, errorCode, retry); + errorDesc, validatedURL) { + loadErrors.showLoadFailure(mainWindow, validatedURL, errorDesc, errorCode, retry); }); addWindowKey(key, mainWindow); diff --git a/tests/getConfig.test.js b/tests/getConfig.test.js index 66094004..55adc3dd 100644 --- a/tests/getConfig.test.js +++ b/tests/getConfig.test.js @@ -1,7 +1,7 @@ const getConfig = require('../js/getconfig'); // mock required so getConfig reads config from correct path -jest.mock('../js/utils', function() { +jest.mock('../js/utils/misc.js', function() { return { isDevEnv: false, isMac: false diff --git a/tests/utils.test.js b/tests/utils.test.js index f6a0429e..61526bb6 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -1,8 +1,9 @@ -const utils = require('../js/utils'); +const getGuid = require('../js/utils/getGuid.js'); +const throttle = require('../js/utils/throttle.js'); describe('guid tests', function() { it('should have valid length', function() { - var guid = utils.getGuid(); + var guid = getGuid(); expect(guid.length).toBe(36); var parts = guid.split('-'); expect(parts.length).toBe(5); @@ -15,7 +16,7 @@ describe('guid tests', function() { it('should only contains hex chars', function() { for(var i = 0; i < 100; i++) { - var guid = utils.getGuid(); + var guid = getGuid(); var parts = guid.split('-'); parts.forEach(function(part) { expect(/^([A-Fa-f0-9]{2})+$/.test(part)).toBe(true); @@ -23,3 +24,64 @@ describe('guid tests', function() { } }); }); + +describe('throttle tests', function() { + var now, origNow; + beforeEach(function() { + origNow = Date.now; + // mock date func + Date.now = function() { return now }; + now = 10000; + }); + + it('expect to be called only once when called more than once in 1 second period', + function() { + jest.useFakeTimers(); + + const callback = jest.fn(); + const throttledCB = throttle(1000, callback); + + expect(callback).not.toBeCalled(); + + throttledCB(); + expect(callback.mock.calls.length).toBe(1); + + throttledCB(); + expect(callback.mock.calls.length).toBe(1); + + now += 1000; + jest.runTimersToTime(1000); + expect(callback.mock.calls.length).toBe(2); + + throttledCB(); + expect(callback.mock.calls.length).toBe(2); + + now += 900; + jest.runTimersToTime(900); + expect(callback.mock.calls.length).toBe(2); + + now += 100; + jest.runTimersToTime(100); + expect(callback.mock.calls.length).toBe(3); + }); + + it('expect to be called twice when call spacing > 1 sec', function() { + const callback = jest.fn(); + const throttledCB = throttle(1000, callback); + + expect(callback).not.toBeCalled(); + + throttledCB(); + expect(callback.mock.calls.length).toBe(1); + + now += 1000; + + throttledCB(); + expect(callback.mock.calls.length).toBe(2); + }); + + afterEach(function() { + // restore orig + Date.now = origNow; + }) +});