diff --git a/__mocks__/Mock.mjs b/__mocks__/Mock.mjs deleted file mode 100644 index ba364bf5..00000000 --- a/__mocks__/Mock.mjs +++ /dev/null @@ -1,97 +0,0 @@ -'use strict'; - -class MockClass -{ - /** - * Construct passing a dictionary of strings to Functions. - * @param {Dictionary} mocks - */ - constructor(mocks) - { - this._mocks = mocks; - this._mocked = {}; - this._originals = {}; - for (const [methodName, method] of Object.entries(this._mocks)) - { - this._mocked[methodName] = false; - this._originals[methodName] = method; - } - } - - /** - * Test related function to enable mocking exported methods - * @param {string} methodName - * @param {Function} stub - */ - mock(methodName, stub) - { - if (!(methodName in this._mocks)) - { - throw Error('Mocking not set for this method'); - } - else - { - this._mocks[methodName] = stub; - this._mocked[methodName] = true; - } - } - - /** - * Test related function to get mocking exported methods - * @param {string} methodName - */ - getMock(methodName) - { - if (!(methodName in this._mocks)) - { - throw Error('Mocking not set for this method'); - } - else - { - if (!this._mocked[methodName]) - { - throw Error('Method not mocked'); - } - return this._mocks[methodName]; - } - } - - /** - * Restore a single mocked method - * @param {string} methodName - */ - restoreMock(methodName) - { - if (!(methodName in this._mocks)) - { - throw Error('Mocking not set for this method'); - } - else - { - if (!this._mocked[methodName]) - { - throw Error('Method not mocked'); - } - this._mocks[methodName] = this._originals[methodName]; - this._mocked[methodName] = false; - } - } - - /** - * Restore all mocked methods - */ - restoreAll() - { - for (const [methodName, isMocked] of Object.entries(this._mocked)) - { - if (isMocked) - { - this.restoreMock(methodName); - } - } - } -} - -export { - MockClass, -}; diff --git a/__tests__/__main__/import-export.mjs b/__tests__/__main__/import-export.mjs index 285ea760..31a78b0c 100644 --- a/__tests__/__main__/import-export.mjs +++ b/__tests__/__main__/import-export.mjs @@ -5,14 +5,43 @@ import Store from 'electron-store'; import fs from 'fs'; import path from 'path'; -import { - exportDatabaseToFile, - importDatabaseFromFile, - validEntry, -} from '../../js/import-export.mjs'; +import ImportExport from '../../js/import-export.mjs'; describe('Import export', function() { + const folder = fs.mkdtempSync('import-export'); + const invalidEntriesFile = path.join(folder, 'invalid.ttldb'); + + const calendarStore = new Store({name: 'flexible-store'}); + const waivedWorkdays = new Store({name: 'waived-workdays'}); + + before(() => + { + calendarStore.clear(); + const entries = { + '2020-3-1': {'values': ['08:00', '12:00', '13:00', '17:00']}, + '2020-3-2': {'values': ['07:00', '11:00', '14:00', '18:00']} + }; + calendarStore.set(entries); + + waivedWorkdays.clear(); + const waivedEntries = { + '2019-12-31': {reason: 'New Year\'s eve', hours: '08:00'}, + '2020-01-01': {reason: 'New Year\'s Day', hours: '08:00'}, + '2020-04-10': {reason: 'Good Friday', hours: '08:00'} + }; + waivedWorkdays.set(waivedEntries); + + const invalidEntriesContent = + `[{"type": "flexible", "date": "not-a-date", "data": "day-begin", "hours": "08:00"}, + {"type": "waived", "date": "2020-01-01", "data": "example waiver 2", "hours": "not-an-hour"}, + {"type": "flexible", "date": "not-a-date", "data": "day-end", "hours": "17:00"}, + {"type": "flexible", "date": "not-a-date", "values": "not-an-array"}, + {"type": "not-a-type", "date": "not-a-date", "data": "day-end", "hours": "17:00"} + ]`; + fs.writeFileSync(invalidEntriesFile, invalidEntriesContent, 'utf8'); + }); + describe('validEntry(entry)', function() { const goodEntry = {'type': 'flexible', 'date': '2020-06-03', 'values': ['08:00', '12:00', '13:00', '14:00']}; @@ -22,66 +51,36 @@ describe('Import export', function() const badWaivedEntry = {'type': 'regular', 'date': '2020-06-03', 'data': 'day-begin', 'hours': 'not-an-hour'}; it('should be valid', () => { - assert.strictEqual(validEntry(goodWaivedEntry), true); - assert.strictEqual(validEntry(goodEntry), true); + assert.strictEqual(ImportExport.validEntry(goodWaivedEntry), true); + assert.strictEqual(ImportExport.validEntry(goodEntry), true); }); it('should not be valid', () => { - assert.strictEqual(validEntry(badWaivedEntry), false); - assert.strictEqual(validEntry(badEntry), false); - assert.strictEqual(validEntry(badEntry2), false); + assert.strictEqual(ImportExport.validEntry(badWaivedEntry), false); + assert.strictEqual(ImportExport.validEntry(badEntry), false); + assert.strictEqual(ImportExport.validEntry(badEntry2), false); }); }); - const calendarStore = new Store({name: 'flexible-store'}); - const waivedWorkdays = new Store({name: 'waived-workdays'}); - - calendarStore.clear(); - const entries = { - '2020-3-1': {'values': ['08:00', '12:00', '13:00', '17:00']}, - '2020-3-2': {'values': ['07:00', '11:00', '14:00', '18:00']} - }; - calendarStore.set(entries); - - waivedWorkdays.clear(); - const waivedEntries = { - '2019-12-31': {reason: 'New Year\'s eve', hours: '08:00'}, - '2020-01-01': {reason: 'New Year\'s Day', hours: '08:00'}, - '2020-04-10': {reason: 'Good Friday', hours: '08:00'} - }; - waivedWorkdays.set(waivedEntries); - - const folder = fs.mkdtempSync('import-export'); - describe('exportDatabaseToFile', function() { it('Check that export works', () => { - assert.strictEqual(exportDatabaseToFile(path.join(folder, 'exported_file.ttldb')), true); - assert.strictEqual(exportDatabaseToFile('/not/a/valid/path'), false); + assert.strictEqual(ImportExport.exportDatabaseToFile(path.join(folder, 'exported_file.ttldb')), true); + assert.strictEqual(ImportExport.exportDatabaseToFile('/not/a/valid/path'), false); }); }); - const invalidEntriesContent = - `[{"type": "flexible", "date": "not-a-date", "data": "day-begin", "hours": "08:00"}, - {"type": "waived", "date": "2020-01-01", "data": "example waiver 2", "hours": "not-an-hour"}, - {"type": "flexible", "date": "not-a-date", "data": "day-end", "hours": "17:00"}, - {"type": "flexible", "date": "not-a-date", "values": "not-an-array"}, - {"type": "not-a-type", "date": "not-a-date", "data": "day-end", "hours": "17:00"} - ]`; - const invalidEntriesFile = path.join(folder, 'invalid.ttldb'); - fs.writeFileSync(invalidEntriesFile, invalidEntriesContent, 'utf8'); - describe('importDatabaseFromFile', function() { it('Check that import works', () => { - assert.strictEqual(importDatabaseFromFile([path.join(folder, 'exported_file.ttldb')])['result'], true); - assert.strictEqual(importDatabaseFromFile(['/not/a/valid/path'])['result'], false); - assert.strictEqual(importDatabaseFromFile(['/not/a/valid/path'])['failed'], 0); - assert.strictEqual(importDatabaseFromFile([invalidEntriesFile])['result'], false); - assert.strictEqual(importDatabaseFromFile([invalidEntriesFile])['failed'], 5); + assert.strictEqual(ImportExport.importDatabaseFromFile([path.join(folder, 'exported_file.ttldb')])['result'], true); + assert.strictEqual(ImportExport.importDatabaseFromFile(['/not/a/valid/path'])['result'], false); + assert.strictEqual(ImportExport.importDatabaseFromFile(['/not/a/valid/path'])['failed'], 0); + assert.strictEqual(ImportExport.importDatabaseFromFile([invalidEntriesFile])['result'], false); + assert.strictEqual(ImportExport.importDatabaseFromFile([invalidEntriesFile])['failed'], 5); }); }); diff --git a/__tests__/__main__/main-window.mjs b/__tests__/__main__/main-window.mjs index a365c082..5263847b 100644 --- a/__tests__/__main__/main-window.mjs +++ b/__tests__/__main__/main-window.mjs @@ -4,7 +4,7 @@ import assert from 'assert'; import { app, BrowserWindow, ipcMain } from 'electron'; import { match, spy, stub, useFakeTimers } from 'sinon'; -import { notificationMock } from '../../js/notification.mjs'; +import Notification from '../../js/notification.mjs'; import { savePreferences, getDefaultPreferences, resetPreferences } from '../../js/user-preferences.mjs'; import { @@ -15,9 +15,7 @@ import { triggerStartupDialogs } from '../../js/main-window.mjs'; -import { updateManagerMock } from '../../js/update-manager.mjs'; -updateManagerMock.mock('checkForUpdates', stub()); -updateManagerMock.mock('shouldCheckForUpdates', stub()); +import UpdateManager from '../../js/update-manager.mjs'; // Mocking USER_DATA_PATH for tests below ipcMain.handle('USER_DATA_PATH', () => @@ -186,29 +184,29 @@ describe('main-window.mjs', () => const mainWindow = getMainWindow(); mainWindow.on('ready-to-show', () => { - notificationMock.mock('createLeaveNotification', stub().callsFake(() => + stub(Notification, 'createLeaveNotification').callsFake(() => { return false; - })); + }); ipcMain.emit('RECEIVE_LEAVE_BY', {}, undefined); - assert.strictEqual(notificationMock.getMock('createLeaveNotification').calledOnce, true); - notificationMock.restoreMock('createLeaveNotification'); + assert.strictEqual(Notification.createLeaveNotification.calledOnce, true); + Notification.createLeaveNotification.restore(); done(); }); }); it('Should show notification', (done) => { - notificationMock.mock('createLeaveNotification', stub().callsFake(() => + stub(Notification, 'createLeaveNotification').callsFake(() => { return { show: () => { - notificationMock.restoreMock('createLeaveNotification'); + Notification.createLeaveNotification.restore(); done(); } }; - })); + }); createWindow(); /** * @type {BrowserWindow} @@ -383,28 +381,28 @@ describe('main-window.mjs', () => { it('Should check for updates and try to migrate', () => { - updateManagerMock.mock('shouldCheckForUpdates', stub().returns(true)); - updateManagerMock.mock('checkForUpdates', stub()); + stub(UpdateManager, 'shouldCheckForUpdates').returns(true); + stub(UpdateManager, 'checkForUpdates'); triggerStartupDialogs(); - assert.strictEqual(updateManagerMock.getMock('shouldCheckForUpdates').calledOnce, true); - assert.strictEqual(updateManagerMock.getMock('checkForUpdates').calledOnce, true); + assert.strictEqual(UpdateManager.shouldCheckForUpdates.calledOnce, true); + assert.strictEqual(UpdateManager.checkForUpdates.calledOnce, true); - updateManagerMock.restoreMock('shouldCheckForUpdates'); - updateManagerMock.restoreMock('checkForUpdates'); + UpdateManager.shouldCheckForUpdates.restore(); + UpdateManager.checkForUpdates.restore(); }); it('Should not check for updates when shouldCheck returns falseZ', () => { - updateManagerMock.mock('shouldCheckForUpdates', stub().returns(false)); - updateManagerMock.mock('checkForUpdates', stub()); + stub(UpdateManager, 'shouldCheckForUpdates').returns(false); + stub(UpdateManager, 'checkForUpdates'); triggerStartupDialogs(); - assert.strictEqual(updateManagerMock.getMock('shouldCheckForUpdates').calledOnce, true); - assert.strictEqual(updateManagerMock.getMock('checkForUpdates').calledOnce, false); + assert.strictEqual(UpdateManager.shouldCheckForUpdates.calledOnce, true); + assert.strictEqual(UpdateManager.checkForUpdates.calledOnce, false); - updateManagerMock.restoreMock('shouldCheckForUpdates'); - updateManagerMock.restoreMock('checkForUpdates'); + UpdateManager.shouldCheckForUpdates.restore(); + UpdateManager.checkForUpdates.restore(); }); }); diff --git a/__tests__/__main__/menus.mjs b/__tests__/__main__/menus.mjs index 12bb686a..b3bc8c0b 100644 --- a/__tests__/__main__/menus.mjs +++ b/__tests__/__main__/menus.mjs @@ -1,4 +1,3 @@ -/* eslint-disable no-undef */ 'use strict'; import assert from 'assert'; @@ -14,20 +13,22 @@ import { getViewMenuTemplate } from '../../js/menus.mjs'; -import { i18nMock } from '../../src/configs/i18next.config.mjs'; -i18nMock.mock('getCurrentTranslation', stub().callsFake((code) => code)); - -import { windowsMock } from '../../js/windows.mjs'; -import { notificationMock } from '../../js/notification.mjs'; -import { updateManagerMock } from '../../js/update-manager.mjs'; -import { importExportMock } from '../../js/import-export.mjs'; +import i18NextConfig from '../../src/configs/i18next.config.mjs'; +import Windows from '../../js/windows.mjs'; +import Notification from '../../js/notification.mjs'; +import UpdateManager from '../../js/update-manager.mjs'; +import ImportExport from '../../js/import-export.mjs'; import Store from 'electron-store'; -stub(Store, 'constructor'); describe('menus.js', () => { const mocks = {}; + before(() => + { + stub(i18NextConfig, 'getCurrentTranslation').callsFake((code) => code); + stub(Store, 'constructor'); + }); describe('getMainMenuTemplate', () => { @@ -36,9 +37,9 @@ describe('menus.js', () => assert.strictEqual(getMainMenuTemplate().length, 3); }); - getMainMenuTemplate().forEach((menu) => + it('Each element should be a separator or valid field', () => { - it('Should be a separator or valid field', () => + getMainMenuTemplate().forEach((menu) => { const tests = [ {field : 'label', type: 'string'}, @@ -68,10 +69,10 @@ describe('menus.js', () => it('Should open waiver window', (done) => { - windowsMock.mock('openWaiverManagerWindow', stub().callsFake(() => + mocks._openWaiverManagerWindow = stub(Windows, 'openWaiverManagerWindow').callsFake(() => { done(); - })); + }); getMainMenuTemplate()[0].click(); }); @@ -93,9 +94,9 @@ describe('menus.js', () => assert.strictEqual(getContextMenuTemplate().length, 3); }); - getContextMenuTemplate().forEach((menu) => + it('Each element should be a valid field', () => { - it('Should be a valid field', () => + getContextMenuTemplate().forEach((menu) => { const tests = [ {field : 'label', type: 'string'}, @@ -119,11 +120,11 @@ describe('menus.js', () => } } }; - notificationMock.mock('createNotification', stub().callsFake(() => ({ + mocks._createNotification = stub(Notification, 'createNotification').callsFake(() => ({ show: stub() - }))); + })); getContextMenuTemplate(mainWindow)[0].click(); - assert.strictEqual(notificationMock.getMock('createNotification').calledOnce, true); + assert.strictEqual(Notification.createNotification.calledOnce, true); }); it('Should create notification on click', (done) => @@ -134,15 +135,17 @@ describe('menus.js', () => getContextMenuTemplate(mainWindow)[1].click(); }); - it('Should show window on click', (done) => + it('Should show window on click', async() => { - const quitStub = stub(app, 'quit').callsFake(() => + await new Promise((resolve) => { - quitStub.restore(); - done(); + mocks._quitStub = stub(app, 'quit').callsFake(() => + { + resolve(); + }); + getContextMenuTemplate({})[2].click(); }); - getContextMenuTemplate({})[2].click(); - expect(mocks.quit).toBeCalledTimes(1); + assert.strictEqual(mocks._quitStub.calledOnce, true); }); }); @@ -153,9 +156,9 @@ describe('menus.js', () => assert.strictEqual(getDockMenuTemplate().length, 1); }); - getDockMenuTemplate().forEach((menu) => + it('Each element should be a valid field', () => { - it('Should be a valid field', () => + getDockMenuTemplate().forEach((menu) => { const tests = [ {field : 'label', type: 'string'}, @@ -178,11 +181,11 @@ describe('menus.js', () => } } }; - notificationMock.mock('createNotification', stub().callsFake(() => ({ + mocks._createNotification = stub(Notification, 'createNotification').callsFake(() => ({ show: done - }))); + })); getDockMenuTemplate(mainWindow)[0].click(); - assert.strictEqual(notificationMock.getMock('createNotification').calledOnce, true); + assert.strictEqual(Notification.createNotification.calledOnce, true); }); }); @@ -193,9 +196,9 @@ describe('menus.js', () => assert.strictEqual(getViewMenuTemplate().length, 2); }); - getViewMenuTemplate().forEach((menu) => + it('Each element should be a valid field', () => { - it('Should be a valid field', () => + getViewMenuTemplate().forEach((menu) => { const tests = [ {field : 'label', type: 'string'}, @@ -250,9 +253,9 @@ describe('menus.js', () => assert.strictEqual(getHelpMenuTemplate().length, 5); }); - getHelpMenuTemplate().forEach((menu) => + it('Each element should be a valid field', () => { - it('Should be a valid field', () => + getHelpMenuTemplate().forEach((menu) => { const tests = [ {field : 'label', type: 'string'}, @@ -285,11 +288,11 @@ describe('menus.js', () => it('Should open github', (done) => { - updateManagerMock.mock('checkForUpdates', stub().callsFake((key) => + mocks._checkForUpdates = stub(UpdateManager, 'checkForUpdates').callsFake((key) => { assert.strictEqual(key, true); done(); - })); + }); getHelpMenuTemplate()[1].click(); }); @@ -367,9 +370,9 @@ describe('menus.js', () => assert.strictEqual(getEditMenuTemplate().length, 10); }); - getEditMenuTemplate().forEach((menu) => + it('Each element should be a separator or valid field', () => { - it('Should be a separator or valid field', () => + getEditMenuTemplate().forEach((menu) => { const tests = [ {field : 'label', type: 'string'}, @@ -407,22 +410,22 @@ describe('menus.js', () => it('Should show dialog for exporting db', () => { const showDialogSyncStub = stub(dialog, 'showSaveDialogSync').returns(true); - importExportMock.mock('exportDatabaseToFile', stub()); + mocks._exportDatabaseTofile = stub(ImportExport, 'exportDatabaseToFile'); getEditMenuTemplate()[7].click(); assert.strictEqual(showDialogSyncStub.calledOnce, true); assert.strictEqual(showMessageBoxStub.calledOnce, true); - assert.strictEqual(importExportMock.getMock('exportDatabaseToFile').calledOnce, true); + assert.strictEqual(ImportExport.exportDatabaseToFile.calledOnce, true); showDialogSyncStub.restore(); }); it('Should not show dialog for exporting db', () => { const showDialogSyncStub = stub(dialog, 'showSaveDialogSync').returns(false); - importExportMock.mock('exportDatabaseToFile', stub()); + mocks._exportDatabaseTofile = stub(ImportExport, 'exportDatabaseToFile'); getEditMenuTemplate()[7].click(); assert.strictEqual(showDialogSyncStub.calledOnce, true); assert.strictEqual(showMessageBoxStub.notCalled, true); - assert.strictEqual(importExportMock.getMock('exportDatabaseToFile').notCalled, true); + assert.strictEqual(ImportExport.exportDatabaseToFile.notCalled, true); showDialogSyncStub.restore(); }); @@ -438,15 +441,15 @@ describe('menus.js', () => }; const showOpenDialogSyncStub = stub(dialog, 'showOpenDialogSync').returns(true); const showMessageBoxSyncStub = stub(dialog, 'showMessageBoxSync').returns(0); - importExportMock.mock('importDatabaseFromFile', stub().returns({ + mocks._importDatabaseFromFile = stub(ImportExport, 'importDatabaseFromFile').returns({ result: true, failed: 0 - })); + }); getEditMenuTemplate(mainWindow)[8].click(); assert.strictEqual(showOpenDialogSyncStub.calledOnce, true); assert.strictEqual(showMessageBoxSyncStub.calledOnce, true); assert.strictEqual(showMessageBoxStub.calledOnce, true); - assert.strictEqual(importExportMock.getMock('importDatabaseFromFile').calledOnce, true); + assert.strictEqual(ImportExport.importDatabaseFromFile.calledOnce, true); showOpenDialogSyncStub.restore(); showMessageBoxSyncStub.restore(); }); @@ -463,15 +466,15 @@ describe('menus.js', () => }; const showOpenDialogSyncStub = stub(dialog, 'showOpenDialogSync').returns(true); const showMessageBoxSyncStub = stub(dialog, 'showMessageBoxSync').returns(0); - importExportMock.mock('importDatabaseFromFile', stub().returns({ + mocks._importDatabaseFromFile = stub(ImportExport, 'importDatabaseFromFile').returns({ result: false, failed: 1 - })); + }); getEditMenuTemplate(mainWindow)[8].click(); assert.strictEqual(showOpenDialogSyncStub.calledOnce, true); assert.strictEqual(showMessageBoxSyncStub.calledTwice, true); assert.strictEqual(showMessageBoxStub.notCalled, true); - assert.strictEqual(importExportMock.getMock('importDatabaseFromFile').calledOnce, true); + assert.strictEqual(ImportExport.importDatabaseFromFile.calledOnce, true); showOpenDialogSyncStub.restore(); showMessageBoxSyncStub.restore(); }); @@ -488,15 +491,15 @@ describe('menus.js', () => }; const showOpenDialogSyncStub = stub(dialog, 'showOpenDialogSync').returns(true); const showMessageBoxSyncStub = stub(dialog, 'showMessageBoxSync').returns(0); - importExportMock.mock('importDatabaseFromFile', stub().returns({ + mocks._importDatabaseFromFile = stub(ImportExport, 'importDatabaseFromFile').returns({ result: false, failed: 0 - })); + }); getEditMenuTemplate(mainWindow)[8].click(); assert.strictEqual(showOpenDialogSyncStub.calledOnce, true); assert.strictEqual(showMessageBoxSyncStub.calledTwice, true); assert.strictEqual(showMessageBoxStub.notCalled, true); - assert.strictEqual(importExportMock.getMock('importDatabaseFromFile').calledOnce, true); + assert.strictEqual(ImportExport.importDatabaseFromFile.calledOnce, true); showOpenDialogSyncStub.restore(); showMessageBoxSyncStub.restore(); }); @@ -505,12 +508,12 @@ describe('menus.js', () => { const showOpenDialogSyncStub = stub(dialog, 'showOpenDialogSync').returns(false); const showMessageBoxSyncStub = stub(dialog, 'showMessageBoxSync').returns(1); - importExportMock.mock('importDatabaseFromFile', stub()); + mocks._importDatabaseFromFile = stub(ImportExport, 'importDatabaseFromFile'); getEditMenuTemplate()[8].click(); assert.strictEqual(showOpenDialogSyncStub.calledOnce, true); assert.strictEqual(showMessageBoxSyncStub.notCalled, true); assert.strictEqual(showMessageBoxStub.notCalled, true); - assert.strictEqual(importExportMock.getMock('importDatabaseFromFile').notCalled, true); + assert.strictEqual(ImportExport.importDatabaseFromFile.notCalled, true); showOpenDialogSyncStub.restore(); showMessageBoxSyncStub.restore(); }); @@ -519,12 +522,12 @@ describe('menus.js', () => { const showOpenDialogSyncStub = stub(dialog, 'showOpenDialogSync').returns(true); const showMessageBoxSyncStub = stub(dialog, 'showMessageBoxSync').returns(1); - importExportMock.mock('importDatabaseFromFile', stub()); + mocks._importDatabaseFromFile = stub(ImportExport, 'importDatabaseFromFile'); getEditMenuTemplate()[8].click(); assert.strictEqual(showOpenDialogSyncStub.calledOnce, true); assert.strictEqual(showMessageBoxSyncStub.calledOnce, true); assert.strictEqual(showMessageBoxStub.notCalled, true); - assert.strictEqual(importExportMock.getMock('importDatabaseFromFile').notCalled, true); + assert.strictEqual(ImportExport.importDatabaseFromFile.notCalled, true); showOpenDialogSyncStub.restore(); showMessageBoxSyncStub.restore(); }); @@ -566,9 +569,15 @@ describe('menus.js', () => afterEach(() => { - importExportMock.restoreAll(); - notificationMock.restoreAll(); - updateManagerMock.restoreAll(); - windowsMock.restoreAll(); + for (const mock of Object.values(mocks)) + { + mock.restore(); + } + }); + + after(() => + { + i18NextConfig.getCurrentTranslation.restore(); + Store.constructor.restore(); }); -}); \ No newline at end of file +}); diff --git a/__tests__/__main__/notification.mjs b/__tests__/__main__/notification.mjs index c385e5b6..deb11614 100644 --- a/__tests__/__main__/notification.mjs +++ b/__tests__/__main__/notification.mjs @@ -4,7 +4,7 @@ import assert from 'assert'; import { app } from 'electron'; import { stub } from 'sinon'; -import { createNotification, createLeaveNotification, updateDismiss, getDismiss } from '../../js/notification.mjs'; +import Notification from '../../js/notification.mjs'; import { getUserPreferences, savePreferences, resetPreferences } from '../../js/user-preferences.mjs'; import { getDateStr } from '../../js/date-aux.mjs'; @@ -19,7 +19,7 @@ describe('Notifications', function() { it('displays a notification in test', (done) => { - const notification = createNotification('test'); + const notification = Notification.createNotification('test'); // On Win32 the notification uses a different specification, with toastXml if (process.platform === 'win32') { @@ -59,7 +59,7 @@ describe('Notifications', function() it('displays a notification in production', (done) => { process.env.NODE_ENV = 'production'; - const notification = createNotification('production'); + const notification = Notification.createNotification('production'); // On Win32 the notification uses a different specification, with toastXml if (process.platform === 'win32') { @@ -106,13 +106,13 @@ describe('Notifications', function() const preferences = getUserPreferences(); preferences['notification'] = false; savePreferences(preferences); - const notify = createLeaveNotification(true); + const notify = Notification.createLeaveNotification(true); assert.strictEqual(notify, false); }); it('Should fail when leaveByElement is not found', () => { - const notify = createLeaveNotification(undefined); + const notify = Notification.createLeaveNotification(undefined); assert.strictEqual(notify, false); }); @@ -120,14 +120,14 @@ describe('Notifications', function() { const now = new Date(); const dateToday = getDateStr(now); - updateDismiss(dateToday); - const notify = createLeaveNotification(true); + Notification.updateDismiss(dateToday); + const notify = Notification.createLeaveNotification(true); assert.strictEqual(notify, false); }); it('Should fail when time is not valid', () => { - const notify = createLeaveNotification('33:90'); + const notify = Notification.createLeaveNotification('33:90'); assert.strictEqual(notify, false); }); @@ -135,7 +135,7 @@ describe('Notifications', function() { const now = new Date(); now.setMinutes(now.getMinutes() + 1); - const notify = createLeaveNotification(buildTimeString(now)); + const notify = Notification.createLeaveNotification(buildTimeString(now)); assert.strictEqual(notify, false); }); @@ -143,7 +143,7 @@ describe('Notifications', function() { const now = new Date(); now.setMinutes(now.getMinutes() - 9); - const notify = createLeaveNotification(buildTimeString(now)); + const notify = Notification.createLeaveNotification(buildTimeString(now)); assert.strictEqual(notify, false); }); @@ -154,47 +154,47 @@ describe('Notifications', function() savePreferences(preferences); const now = new Date(); now.setHours(now.getHours() - 1); - const notify = createLeaveNotification(buildTimeString(now)); + const notify = Notification.createLeaveNotification(buildTimeString(now)); assert.strictEqual(notify, false); }); it('Should pass when time is correct and dismiss action is pressed', () => { const now = new Date(); - const notify = createLeaveNotification(buildTimeString(now)); + const notify = Notification.createLeaveNotification(buildTimeString(now)); assert.notStrictEqual(notify, undefined); - assert.strictEqual(getDismiss(), null); + assert.strictEqual(Notification.getDismiss(), null); assert.strictEqual(notify.listenerCount('action'), 1); assert.strictEqual(notify.listenerCount('close'), 1); assert.strictEqual(notify.listenerCount('click'), 1); notify.emit('action', 'dismiss'); - assert.strictEqual(getDismiss(), getDateStr(now)); + assert.strictEqual(Notification.getDismiss(), getDateStr(now)); }); it('Should pass when time is correct and other action is pressed', () => { const now = new Date(); - const notify = createLeaveNotification(buildTimeString(now)); + const notify = Notification.createLeaveNotification(buildTimeString(now)); assert.notStrictEqual(notify, undefined); - assert.strictEqual(getDismiss(), null); + assert.strictEqual(Notification.getDismiss(), null); assert.strictEqual(notify.listenerCount('action'), 1); assert.strictEqual(notify.listenerCount('close'), 1); assert.strictEqual(notify.listenerCount('click'), 1); notify.emit('action', ''); - assert.strictEqual(getDismiss(), null); + assert.strictEqual(Notification.getDismiss(), null); }); it('Should pass when time is correct and close is pressed', () => { const now = new Date(); - const notify = createLeaveNotification(buildTimeString(now)); + const notify = Notification.createLeaveNotification(buildTimeString(now)); assert.notStrictEqual(notify, undefined); - assert.strictEqual(getDismiss(), null); + assert.strictEqual(Notification.getDismiss(), null); assert.strictEqual(notify.listenerCount('action'), 1); assert.strictEqual(notify.listenerCount('close'), 1); assert.strictEqual(notify.listenerCount('click'), 1); notify.emit('close'); - assert.strictEqual(getDismiss(), getDateStr(now)); + assert.strictEqual(Notification.getDismiss(), getDateStr(now)); }); it('Should pass when time is correct and close is pressed', (done) => @@ -206,7 +206,7 @@ describe('Notifications', function() done(); }); const now = new Date(); - const notify = createLeaveNotification(buildTimeString(now)); + const notify = Notification.createLeaveNotification(buildTimeString(now)); assert.notStrictEqual(notify, undefined); assert.strictEqual(notify.listenerCount('action'), 1); assert.strictEqual(notify.listenerCount('close'), 1); @@ -218,6 +218,6 @@ describe('Notifications', function() afterEach(() => { resetPreferences(); - updateDismiss(null); + Notification.updateDismiss(null); }); }); diff --git a/__tests__/__main__/time-balance.mjs b/__tests__/__main__/time-balance.mjs index 102a59c6..6617746b 100644 --- a/__tests__/__main__/time-balance.mjs +++ b/__tests__/__main__/time-balance.mjs @@ -3,11 +3,7 @@ import assert from 'assert'; import Store from 'electron-store'; -import { - computeAllTimeBalanceUntil, - computeAllTimeBalanceUntilAsync, - getFirstInputInDb -} from '../../js/time-balance.mjs'; +import TimeBalance from '../../js/time-balance.mjs'; import { resetPreferences } from '../../js/user-preferences.mjs'; const calendarStore = new Store({name: 'flexible-store'}); @@ -24,7 +20,7 @@ describe('Time Balance', () => it('getFirstInputInDb: no input', () => { - assert.strictEqual(getFirstInputInDb(), ''); + assert.strictEqual(TimeBalance.getFirstInputInDb(), ''); }); it('getFirstInputInDb: input 1', () => @@ -33,7 +29,7 @@ describe('Time Balance', () => '2020-3-1': {'values': ['08:00']} }; calendarStore.set(entryEx); - assert.strictEqual(getFirstInputInDb(), '2020-3-1'); + assert.strictEqual(TimeBalance.getFirstInputInDb(), '2020-3-1'); }); it('getFirstInputInDb: input 2', () => @@ -43,7 +39,7 @@ describe('Time Balance', () => '2020-3-3': {'values': ['08:00']} }; calendarStore.set(entryEx); - assert.strictEqual(getFirstInputInDb(), '2020-3-1'); + assert.strictEqual(TimeBalance.getFirstInputInDb(), '2020-3-1'); }); it('getFirstInputInDb: input 3', () => @@ -54,7 +50,7 @@ describe('Time Balance', () => '2020-2-1': {'values': ['08:00']} }; calendarStore.set(entryEx); - assert.strictEqual(getFirstInputInDb(), '2020-2-1'); + assert.strictEqual(TimeBalance.getFirstInputInDb(), '2020-2-1'); }); it('getFirstInputInDb: input 4', () => @@ -67,12 +63,12 @@ describe('Time Balance', () => '2020-6-10': {'values': ['08:00', '12:00', '13:00', '19:00']} }; calendarStore.set(entryEx); - assert.strictEqual(getFirstInputInDb(), '2020-6-6'); + assert.strictEqual(TimeBalance.getFirstInputInDb(), '2020-6-6'); }); it('computeAllTimeBalanceUntil: no input', async() => { - assert.strictEqual(await computeAllTimeBalanceUntil(new Date()), '00:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date()), '00:00'); }); it('computeAllTimeBalanceUntil: only regular days', async() => @@ -82,17 +78,17 @@ describe('Time Balance', () => }; calendarStore.set(entryEx); // time balance until thu (excluding thu) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '00:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '00:00'); // time balance until fri (excluding fri) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-08:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-08:00'); // time balance until sat (excluding sat) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-16:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-16:00'); // time balance until sun (excluding sun) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 5)), '-16:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 5)), '-16:00'); // time balance until mon (excluding mon) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 6)), '-16:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 6)), '-16:00'); // time balance until tue (excluding tue) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 7)), '-24:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 7)), '-24:00'); }); it('computeAllTimeBalanceUntil: only regular days, timesAreProgressing false', async() => @@ -102,17 +98,17 @@ describe('Time Balance', () => }; calendarStore.set(entryEx); // time balance until thu (excluding thu) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '-08:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '-08:00'); // time balance until fri (excluding fri) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-16:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-16:00'); // time balance until sat (excluding sat) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-24:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-24:00'); // time balance until sun (excluding sun) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 5)), '-24:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 5)), '-24:00'); // time balance until mon (excluding mon) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 6)), '-24:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 6)), '-24:00'); // time balance until tue (excluding tue) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 7)), '-32:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 7)), '-32:00'); }); it('computeAllTimeBalanceUntil: only regular days (6 entries)', async() => @@ -122,17 +118,17 @@ describe('Time Balance', () => }; calendarStore.set(entryEx); // time balance until thu (excluding thu) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '-01:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '-01:00'); // time balance until fri (excluding fri) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-09:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-09:00'); // time balance until sat (excluding sat) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-17:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-17:00'); // time balance until sun (excluding sun) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 5)), '-17:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 5)), '-17:00'); // time balance until mon (excluding mon) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 6)), '-17:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 6)), '-17:00'); // time balance until tue (excluding tue) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 7)), '-25:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 7)), '-25:00'); }); it('computeAllTimeBalanceUntil: only regular days (with overtime)', async() => @@ -142,11 +138,11 @@ describe('Time Balance', () => }; calendarStore.set(entryEx); // time balance until thu (excluding thu) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '01:30'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '01:30'); // time balance until fri (excluding fri) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-06:30'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-06:30'); // time balance until sat (excluding sat) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-14:30'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-14:30'); }); it('computeAllTimeBalanceUntil: only regular days (with overtime and 8 entries)', async() => @@ -156,11 +152,11 @@ describe('Time Balance', () => }; calendarStore.set(entryEx); // time balance until thu (excluding thu) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '02:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '02:00'); // time balance until fri (excluding fri) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-06:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-06:00'); // time balance until sat (excluding sat) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-14:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-14:00'); }); it('computeAllTimeBalanceUntil: only regular days (with undertime)', async() => @@ -170,11 +166,11 @@ describe('Time Balance', () => }; calendarStore.set(entryEx); // time balance until thu (excluding thu) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '-01:45'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '-01:45'); // time balance until fri (excluding fri) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-09:45'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-09:45'); // time balance until sat (excluding sat) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-17:45'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-17:45'); }); it('computeAllTimeBalanceUntil: only regular days (with mixed time)', async() => @@ -186,11 +182,11 @@ describe('Time Balance', () => }; calendarStore.set(entryEx); // time balance until thu (excluding thu) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '-01:45'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '-01:45'); // time balance until fri (excluding fri) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-00:30'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-00:30'); // time balance until sat (excluding sat) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-02:15'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-02:15'); }); it('computeAllTimeBalanceUntil: irregular days (with mixed time)', async() => @@ -205,21 +201,21 @@ describe('Time Balance', () => }; calendarStore.set(entryEx); // time balance until mon (excluding mon) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 6)), '00:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 6)), '00:00'); // time balance until tue (excluding tue) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 7)), '-04:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 7)), '-04:00'); // time balance until wed (excluding wed) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 8)), '-02:45'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 8)), '-02:45'); // time balance until thu (excluding thu) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 9)), '-04:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 9)), '-04:00'); // time balance until fru (excluding fri) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 10)), '-12:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 10)), '-12:00'); // time balance until sat/sun/mon (excluding sat/sun/mon) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 11)), '-20:00'); - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 12)), '-20:00'); - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 13)), '-20:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 11)), '-20:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 12)), '-20:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 13)), '-20:00'); // time balance until tue (excluding tue) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 14)), '-10:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 14)), '-10:00'); }); it('computeAllTimeBalanceUntil: irregular (but even) days (with mixed time)', async() => @@ -234,21 +230,21 @@ describe('Time Balance', () => }; calendarStore.set(entryEx); // time balance until mon (excluding mon) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 6)), '00:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 6)), '00:00'); // time balance until tue (excluding tue) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 7)), '-04:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 7)), '-04:00'); // time balance until wed (excluding wed) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 8)), '-02:45'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 8)), '-02:45'); // time balance until thu (excluding thu) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 9)), '-04:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 9)), '-04:00'); // time balance until fri (excluding fri) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 10)), '-08:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 10)), '-08:00'); // time balance until sat/sun/mon (excluding sat/sun/mon) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 11)), '-10:00'); - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 12)), '-10:00'); - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 13)), '-10:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 11)), '-10:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 12)), '-10:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 13)), '-10:00'); // time balance until tue (excluding tue) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 14)), '00:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 14)), '00:00'); }); it('computeAllTimeBalanceUntil: missing entries', async() => @@ -259,13 +255,13 @@ describe('Time Balance', () => }; calendarStore.set(entryEx); // time balance until thu (excluding thu) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '00:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '00:00'); // time balance until fri (excluding fri) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-08:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-08:00'); // time balance until sat (excluding sat) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-08:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-08:00'); // time balance until sun (excluding sun) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 5)), '-08:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 5)), '-08:00'); }); it('computeAllTimeBalanceUntil: with waived days', async() => @@ -280,11 +276,11 @@ describe('Time Balance', () => }; waivedWorkdays.set(waivedEntries); // time balance until thu (excluding thu) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '00:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '00:00'); // time balance until fri (excluding fri) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '00:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '00:00'); // time balance until sat (excluding sat) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '00:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '00:00'); }); it('computeAllTimeBalanceUntil: with waived days 2', async() => @@ -299,13 +295,13 @@ describe('Time Balance', () => }; waivedWorkdays.set(waivedEntries); // time balance until wed (excluding wed) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 8)), '00:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 8)), '00:00'); // time balance until tue (excluding tue) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 9)), '00:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 9)), '00:00'); // time balance until fri (excluding fri) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 10)), '00:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 10)), '00:00'); // time balance until sat (excluding sat) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 11)), '00:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 11)), '00:00'); }); it('computeAllTimeBalanceUntil: with waived days (not full)', async() => @@ -320,11 +316,11 @@ describe('Time Balance', () => }; waivedWorkdays.set(waivedEntries); // time balance until tue (excluding tue) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '00:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 2)), '00:00'); // time balance until fri (excluding fri) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-06:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 3)), '-06:00'); // time balance until sat (excluding sat) - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-06:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 6, 4)), '-06:00'); }); it('computeAllTimeBalanceUntil: target date in the past of entries', async() => @@ -338,7 +334,7 @@ describe('Time Balance', () => '2020-07-02': { reason: 'Waiver', hours: '02:00' }, // tue }; waivedWorkdays.set(waivedEntries); - assert.strictEqual(await computeAllTimeBalanceUntil(new Date(2020, 5, 1)), '00:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntil(new Date(2020, 5, 1)), '00:00'); }); it('computeAllTimeBalanceUntilAsync: target date in the past of entries', async() => @@ -352,7 +348,7 @@ describe('Time Balance', () => '2020-07-02': { reason: 'Waiver', hours: '02:00' }, // tue }; waivedWorkdays.set(waivedEntries); - assert.strictEqual(await computeAllTimeBalanceUntilAsync(new Date(2020, 5, 1)), '00:00'); + assert.strictEqual(await TimeBalance.computeAllTimeBalanceUntilAsync(new Date(2020, 5, 1)), '00:00'); }); }); diff --git a/__tests__/__main__/time-math.mjs b/__tests__/__main__/time-math.mjs index 990d9108..b1a7a993 100644 --- a/__tests__/__main__/time-math.mjs +++ b/__tests__/__main__/time-math.mjs @@ -2,17 +2,7 @@ import assert from 'assert'; -import { - diffDays, - hourToMinutes, - isNegative, - minutesToHourFormatted, - multiplyTime, - subtractTime, - sumTime, - validateDate, - validateTime, -} from '../../js/time-math.mjs'; +import TimeMath from '../../js/time-math.mjs'; const date1 = new Date(-349891200000); const date2 = new Date(1581121289763); @@ -24,12 +14,12 @@ describe('Time Math Functions', () => { it('expect diffDays 22350', () => { - assert.strictEqual(diffDays(date1, date2), 22350); + assert.strictEqual(TimeMath.diffDays(date1, date2), 22350); }); it('expect diffDays greater than 0', () => { - assert.strictEqual(diffDays(date1, date3) > 0, true); + assert.strictEqual(TimeMath.diffDays(date1, date3) > 0, true); }); }); @@ -37,12 +27,12 @@ describe('Time Math Functions', () => { it('date1 Should not be negative', () => { - assert.strictEqual(isNegative(date2), false); + assert.strictEqual(TimeMath.isNegative(date2), false); }); it('-date2 Should be negative', () => { - assert.strictEqual(isNegative('-' + date2), true); + assert.strictEqual(TimeMath.isNegative('-' + date2), true); }); }); @@ -50,32 +40,32 @@ describe('Time Math Functions', () => { it('0 should return 00:00', () => { - assert.strictEqual(minutesToHourFormatted(0), '00:00'); - assert.strictEqual(minutesToHourFormatted(-0), '00:00'); + assert.strictEqual(TimeMath.minutesToHourFormatted(0), '00:00'); + assert.strictEqual(TimeMath.minutesToHourFormatted(-0), '00:00'); }); it('1 should return 00:01', () => { - assert.strictEqual(minutesToHourFormatted(1), '00:01'); - assert.strictEqual(minutesToHourFormatted(-1), '-00:01'); + assert.strictEqual(TimeMath.minutesToHourFormatted(1), '00:01'); + assert.strictEqual(TimeMath.minutesToHourFormatted(-1), '-00:01'); }); it('59 should return 00:59', () => { - assert.strictEqual(minutesToHourFormatted(59), '00:59'); - assert.strictEqual(minutesToHourFormatted(-59), '-00:59'); + assert.strictEqual(TimeMath.minutesToHourFormatted(59), '00:59'); + assert.strictEqual(TimeMath.minutesToHourFormatted(-59), '-00:59'); }); it('60 should return 01:00', () => { - assert.strictEqual(minutesToHourFormatted(60), '01:00'); - assert.strictEqual(minutesToHourFormatted(-60), '-01:00'); + assert.strictEqual(TimeMath.minutesToHourFormatted(60), '01:00'); + assert.strictEqual(TimeMath.minutesToHourFormatted(-60), '-01:00'); }); it('61 should return 01:01', () => { - assert.strictEqual(minutesToHourFormatted(61), '01:01'); - assert.strictEqual(minutesToHourFormatted(-61), '-01:01'); + assert.strictEqual(TimeMath.minutesToHourFormatted(61), '01:01'); + assert.strictEqual(TimeMath.minutesToHourFormatted(-61), '-01:01'); }); }); @@ -84,32 +74,32 @@ describe('Time Math Functions', () => { it('00:00 should return 0', () => { - assert.strictEqual(hourToMinutes('00:00'), 0); - assert.strictEqual(hourToMinutes('-00:00') < 1, true); + assert.strictEqual(TimeMath.hourToMinutes('00:00'), 0); + assert.strictEqual(TimeMath.hourToMinutes('-00:00') < 1, true); }); it('01:01 should return 61', () => { - assert.strictEqual(hourToMinutes('01:01'), 61); - assert.strictEqual(hourToMinutes('-01:01'), -61); + assert.strictEqual(TimeMath.hourToMinutes('01:01'), 61); + assert.strictEqual(TimeMath.hourToMinutes('-01:01'), -61); }); it('00:01 should return 1', () => { - assert.strictEqual(hourToMinutes('00:01'), 1); - assert.strictEqual(hourToMinutes('-00:01'), -1); + assert.strictEqual(TimeMath.hourToMinutes('00:01'), 1); + assert.strictEqual(TimeMath.hourToMinutes('-00:01'), -1); }); it('00:59 should return 59', () => { - assert.strictEqual(hourToMinutes('00:59'), 59); - assert.strictEqual(hourToMinutes('-00:59'), -59); + assert.strictEqual(TimeMath.hourToMinutes('00:59'), 59); + assert.strictEqual(TimeMath.hourToMinutes('-00:59'), -59); }); it('01:00 should return 60', () => { - assert.strictEqual(hourToMinutes('01:00'), 60); - assert.strictEqual(hourToMinutes('-01:00'), -60); + assert.strictEqual(TimeMath.hourToMinutes('01:00'), 60); + assert.strictEqual(TimeMath.hourToMinutes('-01:00'), -60); }); }); @@ -118,59 +108,59 @@ describe('Time Math Functions', () => { it('01:00 * 10 should be 10:00', () => { - assert.strictEqual(multiplyTime('01:00', 10), '10:00'); - assert.strictEqual(multiplyTime('-01:00', 10), '-10:00'); - assert.strictEqual(multiplyTime('01:00', -10), '-10:00'); + assert.strictEqual(TimeMath.multiplyTime('01:00', 10), '10:00'); + assert.strictEqual(TimeMath.multiplyTime('-01:00', 10), '-10:00'); + assert.strictEqual(TimeMath.multiplyTime('01:00', -10), '-10:00'); }); it('00:60 * 1 should be 01:00', () => { - assert.strictEqual(multiplyTime('00:60', 1), '01:00'); - assert.strictEqual(multiplyTime('-00:60', 1), '-01:00'); - assert.strictEqual(multiplyTime('00:60', -1), '-01:00'); + assert.strictEqual(TimeMath.multiplyTime('00:60', 1), '01:00'); + assert.strictEqual(TimeMath.multiplyTime('-00:60', 1), '-01:00'); + assert.strictEqual(TimeMath.multiplyTime('00:60', -1), '-01:00'); }); }); // Subtract time it('subtractTime(HH:MM, HH:MM)', () => { - assert.strictEqual(subtractTime('1:00', '1:00'), '00:00'); - assert.strictEqual(subtractTime('00:00', '00:00'), '00:00'); - assert.strictEqual(subtractTime('00:01', '01:00'), '00:59'); - assert.strictEqual(subtractTime('13:00', '12:00'), '-01:00'); - assert.strictEqual(subtractTime('48:00', '24:00'), '-24:00'); - assert.strictEqual(subtractTime('00:01', '12:00'), '11:59'); - assert.strictEqual(subtractTime('12:00', '13:00'), '01:00'); - assert.strictEqual(subtractTime('13:00', '00:00'), '-13:00'); + assert.strictEqual(TimeMath.subtractTime('1:00', '1:00'), '00:00'); + assert.strictEqual(TimeMath.subtractTime('00:00', '00:00'), '00:00'); + assert.strictEqual(TimeMath.subtractTime('00:01', '01:00'), '00:59'); + assert.strictEqual(TimeMath.subtractTime('13:00', '12:00'), '-01:00'); + assert.strictEqual(TimeMath.subtractTime('48:00', '24:00'), '-24:00'); + assert.strictEqual(TimeMath.subtractTime('00:01', '12:00'), '11:59'); + assert.strictEqual(TimeMath.subtractTime('12:00', '13:00'), '01:00'); + assert.strictEqual(TimeMath.subtractTime('13:00', '00:00'), '-13:00'); }); // Sum time it('sumTime(HH:MM, HH:MM)', () => { - assert.strictEqual(sumTime('01:00', '01:00'), '02:00'); - assert.strictEqual(sumTime('00:00', '00:00'), '00:00'); - assert.strictEqual(sumTime('00:00', '00:01'), '00:01'); - assert.strictEqual(sumTime('00:59', '00:01'), '01:00'); - assert.strictEqual(sumTime('12:00', '12:00'), '24:00'); - assert.strictEqual(sumTime('12:00', '-12:00'), '00:00'); + assert.strictEqual(TimeMath.sumTime('01:00', '01:00'), '02:00'); + assert.strictEqual(TimeMath.sumTime('00:00', '00:00'), '00:00'); + assert.strictEqual(TimeMath.sumTime('00:00', '00:01'), '00:01'); + assert.strictEqual(TimeMath.sumTime('00:59', '00:01'), '01:00'); + assert.strictEqual(TimeMath.sumTime('12:00', '12:00'), '24:00'); + assert.strictEqual(TimeMath.sumTime('12:00', '-12:00'), '00:00'); }); // Time Validation it('validateTime(HH:MM)', () => { - assert.strictEqual(validateTime('00:00'), true); - assert.strictEqual(validateTime('00:01'), true); - assert.strictEqual(validateTime('00:11'), true); - assert.strictEqual(validateTime('01:11'), true); - assert.strictEqual(validateTime('11:11'), true); - assert.strictEqual(validateTime('23:59'), true); - assert.strictEqual(validateTime('-04:00'), true); - assert.strictEqual(validateTime('24:00'), false); - assert.strictEqual(validateTime('34:00'), false); - assert.strictEqual(validateTime('4:00'), false); - assert.strictEqual(validateTime('00:1'), false); - assert.strictEqual(validateTime('--:--'), false); - assert.strictEqual(validateTime(''), false); + assert.strictEqual(TimeMath.validateTime('00:00'), true); + assert.strictEqual(TimeMath.validateTime('00:01'), true); + assert.strictEqual(TimeMath.validateTime('00:11'), true); + assert.strictEqual(TimeMath.validateTime('01:11'), true); + assert.strictEqual(TimeMath.validateTime('11:11'), true); + assert.strictEqual(TimeMath.validateTime('23:59'), true); + assert.strictEqual(TimeMath.validateTime('-04:00'), true); + assert.strictEqual(TimeMath.validateTime('24:00'), false); + assert.strictEqual(TimeMath.validateTime('34:00'), false); + assert.strictEqual(TimeMath.validateTime('4:00'), false); + assert.strictEqual(TimeMath.validateTime('00:1'), false); + assert.strictEqual(TimeMath.validateTime('--:--'), false); + assert.strictEqual(TimeMath.validateTime(''), false); }); it('validateDate(date)', () => @@ -213,7 +203,7 @@ describe('Time Math Functions', () => ]; for (const test of tests) { - assert.strictEqual(validateDate(test.date), test.valid); + assert.strictEqual(TimeMath.validateDate(test.date), test.valid); } }); }); diff --git a/__tests__/__main__/update-manager.mjs b/__tests__/__main__/update-manager.mjs index 48800d75..1cc9e458 100644 --- a/__tests__/__main__/update-manager.mjs +++ b/__tests__/__main__/update-manager.mjs @@ -6,7 +6,7 @@ import { stub } from 'sinon'; import Store from 'electron-store'; import { getDateStr } from '../../js/date-aux.mjs'; -import { checkForUpdates, shouldCheckForUpdates } from '../../js/update-manager.mjs'; +import UpdateManager from '../../js/update-manager.mjs'; describe('Update Manager', () => { @@ -16,7 +16,7 @@ describe('Update Manager', () => { const store = new Store(); store.set('update-remind-me-after', false); - assert.strictEqual(shouldCheckForUpdates(), true); + assert.strictEqual(UpdateManager.shouldCheckForUpdates(), true); }); it('Should return true when was checked before today', () => @@ -25,7 +25,7 @@ describe('Update Manager', () => now.setDate(now.getDate() - 1); const store = new Store(); store.set('update-remind-me-after', getDateStr(now)); - assert.strictEqual(shouldCheckForUpdates(), true); + assert.strictEqual(UpdateManager.shouldCheckForUpdates(), true); }); it('Should return false when was checked today', () => @@ -33,7 +33,7 @@ describe('Update Manager', () => const now = new Date(); const store = new Store(); store.set('update-remind-me-after', getDateStr(now)); - assert.strictEqual(shouldCheckForUpdates(), false); + assert.strictEqual(UpdateManager.shouldCheckForUpdates(), false); }); }); @@ -45,7 +45,7 @@ describe('Update Manager', () => on: () => {}, end: () => {} }); - checkForUpdates(); + UpdateManager.checkForUpdates(); assert.strictEqual(netStub.notCalled, true); netStub.restore(); }); @@ -61,7 +61,7 @@ describe('Update Manager', () => }, end: () => {} }); - checkForUpdates(); + UpdateManager.checkForUpdates(); }); }); }); \ No newline at end of file diff --git a/__tests__/__main__/windows.mjs b/__tests__/__main__/windows.mjs index 00f63880..9930a6dc 100644 --- a/__tests__/__main__/windows.mjs +++ b/__tests__/__main__/windows.mjs @@ -5,12 +5,7 @@ import { BrowserWindow } from 'electron'; import { stub } from 'sinon'; import { getDateStr } from '../../js/date-aux.mjs'; -import { - getDialogCoordinates, - getWaiverWindow, - openWaiverManagerWindow, - resetWindowsElements, -} from '../../js/windows.mjs'; +import Windows from '../../js/windows.mjs'; describe('Windows tests', () => { @@ -26,7 +21,7 @@ describe('Windows tests', () => it('Elements should be null on starting', () => { - assert.strictEqual(getWaiverWindow(), null); + assert.strictEqual(Windows.getWaiverWindow(), null); assert.strictEqual(global.tray, null); assert.strictEqual(global.contextMenu, null); assert.strictEqual(global.prefWindow, null); @@ -37,12 +32,12 @@ describe('Windows tests', () => const mainWindow = new BrowserWindow({ show: false }); - openWaiverManagerWindow(mainWindow); - assert.notStrictEqual(getWaiverWindow(), null); - assert.strictEqual(getWaiverWindow() instanceof BrowserWindow, true); + Windows.openWaiverManagerWindow(mainWindow); + assert.notStrictEqual(Windows.getWaiverWindow(), null); + assert.strictEqual(Windows.getWaiverWindow() instanceof BrowserWindow, true); // Values can vary about 10px from 600, 500 - const size = getWaiverWindow().getSize(); + const size = Windows.getWaiverWindow().getSize(); assert.strictEqual(Math.abs(size[0] - 600) < 10, true); assert.strictEqual(Math.abs(size[1] - 500) < 10, true); @@ -57,9 +52,9 @@ describe('Windows tests', () => const mainWindow = new BrowserWindow({ show: false }); - openWaiverManagerWindow(mainWindow); - openWaiverManagerWindow(mainWindow); - assert.notStrictEqual(getWaiverWindow(), null); + Windows.openWaiverManagerWindow(mainWindow); + Windows.openWaiverManagerWindow(mainWindow); + assert.notStrictEqual(Windows.getWaiverWindow(), null); // It should only load once the URL because it already exists assert.strictEqual(showSpy.calledTwice, true); assert.strictEqual(loadSpy.calledOnce, true); @@ -71,8 +66,8 @@ describe('Windows tests', () => const mainWindow = new BrowserWindow({ show: false }); - openWaiverManagerWindow(mainWindow, true); - assert.notStrictEqual(getWaiverWindow(), null); + Windows.openWaiverManagerWindow(mainWindow, true); + assert.notStrictEqual(Windows.getWaiverWindow(), null); assert.strictEqual(global.waiverDay, getDateStr(new Date())); done(); }); @@ -82,14 +77,14 @@ describe('Windows tests', () => const mainWindow = new BrowserWindow({ show: false }); - openWaiverManagerWindow(mainWindow, true); - getWaiverWindow().emit('close'); - assert.strictEqual(getWaiverWindow(), null); + Windows.openWaiverManagerWindow(mainWindow, true); + Windows.getWaiverWindow().emit('close'); + assert.strictEqual(Windows.getWaiverWindow(), null); }); it('Should get dialog coordinates', () => { - const coordinates = getDialogCoordinates(500, 250, { + const coordinates = Windows.getDialogCoordinates(500, 250, { getBounds: () => ({ x: 200, y: 300, @@ -107,7 +102,7 @@ describe('Windows tests', () => { showSpy.resetHistory(); loadSpy.resetHistory(); - resetWindowsElements(); + Windows.resetWindowsElements(); }); after(() => diff --git a/__tests__/__renderer__/classes/BaseCalendar.mjs b/__tests__/__renderer__/classes/BaseCalendar.mjs index 84bcc641..442cac5f 100644 --- a/__tests__/__renderer__/classes/BaseCalendar.mjs +++ b/__tests__/__renderer__/classes/BaseCalendar.mjs @@ -14,10 +14,10 @@ import { savePreferences, switchCalendarView } from '../../../js/user-preferences.mjs'; -import { computeAllTimeBalanceUntilAsync, timeBalanceMock } from '../../../js/time-balance.mjs'; +import TimeBalance from '../../../js/time-balance.mjs'; import { calendarApi } from '../../../renderer/preload-scripts/calendar-api.mjs'; -import { timeMathMock } from '../../../js/time-math.mjs'; +import TimeMath from '../../../js/time-math.mjs'; const $_backup = global.$; @@ -49,7 +49,7 @@ describe('BaseCalendar.js', () => window.mainApi.computeAllTimeBalanceUntilPromise = (targetDate) => { - return computeAllTimeBalanceUntilAsync(targetDate); + return TimeBalance.computeAllTimeBalanceUntilAsync(targetDate); }; window.mainApi.switchView = () => @@ -162,23 +162,23 @@ describe('BaseCalendar.js', () => it('Should not update value because of no implementation', () => { delete ExtendedClass.prototype._getTargetDayForAllTimeBalance; - timeBalanceMock.mock('computeAllTimeBalanceUntilAsync', stub().resolves()); + mocks._computeAllTimeBalanceUntilAsyn = stub(TimeBalance, 'computeAllTimeBalanceUntilAsync').resolves(); const preferences = {view: 'day'}; const languageData = {hello: 'hola'}; const calendar = new ExtendedClass(preferences, languageData); assert.throws(() => calendar._updateAllTimeBalance(), /Please implement this\.$/); - assert.strictEqual(timeBalanceMock.getMock('computeAllTimeBalanceUntilAsync').notCalled, true); + assert.strictEqual(TimeBalance.computeAllTimeBalanceUntilAsync.notCalled, true); }); it('Should not update value because of rejection', () => { stub(console, 'log'); - timeBalanceMock.mock('computeAllTimeBalanceUntilAsync', stub().rejects('Error')); + mocks._computeAllTimeBalanceUntilAsyn = stub(TimeBalance, 'computeAllTimeBalanceUntilAsync').rejects('Error'); const preferences = {view: 'day'}; const languageData = {hello: 'hola'}; const calendar = new ExtendedClass(preferences, languageData); calendar._updateAllTimeBalance(); - assert.strictEqual(timeBalanceMock.getMock('computeAllTimeBalanceUntilAsync').calledOnce, true); + assert.strictEqual(TimeBalance.computeAllTimeBalanceUntilAsync.calledOnce, true); // When the rejection happens, we call console.log with the error value setTimeout(() => @@ -191,29 +191,29 @@ describe('BaseCalendar.js', () => it('Should not update value because no overall-balance element', () => { global.$ = () => false; - timeBalanceMock.mock('computeAllTimeBalanceUntilAsync', stub().resolves('2022-02-31')); - timeMathMock.mock('isNegative', stub().returns(true)); + mocks._computeAllTimeBalanceUntilAsyn = stub(TimeBalance, 'computeAllTimeBalanceUntilAsync').resolves('2022-02-31'); + mocks._isNegative = stub(TimeMath, 'isNegative').returns(true); const preferences = {view: 'day'}; const languageData = {hello: 'hola'}; const calendar = new ExtendedClass(preferences, languageData); calendar._updateAllTimeBalance(); - assert.strictEqual(timeMathMock.getMock('isNegative').notCalled, true); - assert.strictEqual(timeBalanceMock.getMock('computeAllTimeBalanceUntilAsync').calledOnce, true); + assert.strictEqual(TimeMath.isNegative.notCalled, true); + assert.strictEqual(TimeBalance.computeAllTimeBalanceUntilAsync.calledOnce, true); }); it('Should update value with text-danger class', (done) => { $('body').append('12:12'); $('#overall-balance').val('12:12'); - timeBalanceMock.mock('computeAllTimeBalanceUntilAsync', stub().resolves('2022-02-31')); - timeMathMock.mock('isNegative', stub().returns(true)); + mocks._computeAllTimeBalanceUntilAsyn = stub(TimeBalance, 'computeAllTimeBalanceUntilAsync').resolves('2022-02-31'); + mocks._isNegative = stub(TimeMath, 'isNegative').returns(true); const preferences = {view: 'day'}; const languageData = {hello: 'hola'}; const calendar = new ExtendedClass(preferences, languageData); calendar._updateAllTimeBalance(); setTimeout(() => { - assert.strictEqual(timeBalanceMock.getMock('computeAllTimeBalanceUntilAsync').calledOnce, true); + assert.strictEqual(TimeBalance.computeAllTimeBalanceUntilAsync.calledOnce, true); assert.strictEqual($('#overall-balance').val(), '2022-02-31'); assert.strictEqual($('#overall-balance').hasClass('text-danger'), true); assert.strictEqual($('#overall-balance').hasClass('text-success'), false); @@ -225,15 +225,15 @@ describe('BaseCalendar.js', () => { $('body').append('12:12'); $('#overall-balance').val('12:12'); - timeBalanceMock.mock('computeAllTimeBalanceUntilAsync', stub().resolves('2022-02-31')); - timeMathMock.mock('isNegative', stub().returns(false)); + mocks._computeAllTimeBalanceUntilAsyn = stub(TimeBalance, 'computeAllTimeBalanceUntilAsync').resolves('2022-02-31'); + mocks._isNegative = stub(TimeMath, 'isNegative').returns(false); const preferences = {view: 'day'}; const languageData = {hello: 'hola'}; const calendar = new ExtendedClass(preferences, languageData); calendar._updateAllTimeBalance(); setTimeout(() => { - assert.strictEqual(timeBalanceMock.getMock('computeAllTimeBalanceUntilAsync').calledOnce, true); + assert.strictEqual(TimeBalance.computeAllTimeBalanceUntilAsync.calledOnce, true); assert.strictEqual($('#overall-balance').val(), '2022-02-31'); assert.strictEqual($('#overall-balance').hasClass('text-danger'), false); assert.strictEqual($('#overall-balance').hasClass('text-success'), true); @@ -491,8 +491,6 @@ describe('BaseCalendar.js', () => { mock.restore(); } - timeBalanceMock.restoreAll(); - timeMathMock.restoreAll(); global.$ = $_backup; $('#overall-balance').remove(); resetPreferences(); diff --git a/__tests__/__renderer__/classes/DayCalendar.mjs b/__tests__/__renderer__/classes/DayCalendar.mjs index cee57878..379a3888 100644 --- a/__tests__/__renderer__/classes/DayCalendar.mjs +++ b/__tests__/__renderer__/classes/DayCalendar.mjs @@ -5,7 +5,7 @@ import '../../../__mocks__/jquery.mjs'; import assert from 'assert'; import Store from 'electron-store'; -import { computeAllTimeBalanceUntilAsync } from '../../../js/time-balance.mjs'; +import TimeBalance from '../../../js/time-balance.mjs'; import { getDefaultPreferences } from '../../../js/user-preferences.mjs'; import { BaseCalendar } from '../../../renderer/classes/BaseCalendar.js'; import { CalendarFactory } from '../../../renderer/classes/CalendarFactory.js'; @@ -76,7 +76,7 @@ describe('DayCalendar class Tests', () => { return new Promise((resolve) => { - resolve(computeAllTimeBalanceUntilAsync(targetDate)); + resolve(TimeBalance.computeAllTimeBalanceUntilAsync(targetDate)); }); }; }); diff --git a/__tests__/__renderer__/classes/MonthCalendar.mjs b/__tests__/__renderer__/classes/MonthCalendar.mjs index 6aab13f7..9e9a0813 100644 --- a/__tests__/__renderer__/classes/MonthCalendar.mjs +++ b/__tests__/__renderer__/classes/MonthCalendar.mjs @@ -5,7 +5,7 @@ import '../../../__mocks__/jquery.mjs'; import assert from 'assert'; import Store from 'electron-store'; -import { computeAllTimeBalanceUntilAsync } from '../../../js/time-balance.mjs'; +import TimeBalance from '../../../js/time-balance.mjs'; import { getDefaultPreferences } from '../../../js/user-preferences.mjs'; import { BaseCalendar } from '../../../renderer/classes/BaseCalendar.js'; import { CalendarFactory } from '../../../renderer/classes/CalendarFactory.js'; @@ -75,7 +75,7 @@ describe('MonthCalendar class Tests', () => { return new Promise((resolve) => { - resolve(computeAllTimeBalanceUntilAsync(targetDate)); + resolve(TimeBalance.computeAllTimeBalanceUntilAsync(targetDate)); }); }; }); diff --git a/__tests__/__renderer__/preferences.mjs b/__tests__/__renderer__/preferences.mjs index 8685ef03..576788c2 100644 --- a/__tests__/__renderer__/preferences.mjs +++ b/__tests__/__renderer__/preferences.mjs @@ -5,7 +5,7 @@ import '../../__mocks__/jquery.mjs'; import assert from 'assert'; import fs from 'fs'; import { JSDOM } from 'jsdom'; -import sinon from 'sinon'; +import { stub } from 'sinon'; import path from 'path'; import { rootDir } from '../../js/app-config.mjs'; @@ -16,8 +16,7 @@ import { savePreferences, } from '../../js/user-preferences.mjs'; import { preferencesApi } from '../../renderer/preload-scripts/preferences-api.mjs'; -import { i18nTranslatorMock } from '../../renderer/i18n-translator.js'; -i18nTranslatorMock.mock('getTranslationInLanguageData', sinon.stub().returnsThis()); +import i18nTranslator from '../../renderer/i18n-translator.js'; const isCheckBox = true; const weekdays = [ @@ -93,6 +92,8 @@ describe('Test Preferences Window', () => { before(async() => { + stub(i18nTranslator, 'getTranslationInLanguageData').returnsThis(); + // APIs from the preload script of the preferences window window.mainApi = preferencesApi; @@ -331,4 +332,9 @@ describe('Test Preferences Window', () => }, 1); }); }); + + after(() => + { + i18nTranslator.getTranslationInLanguageData.restore(); + }); }); diff --git a/__tests__/__renderer__/workday-waiver.mjs b/__tests__/__renderer__/workday-waiver.mjs index 43c67b2a..6b9b69f2 100644 --- a/__tests__/__renderer__/workday-waiver.mjs +++ b/__tests__/__renderer__/workday-waiver.mjs @@ -7,7 +7,7 @@ import assert from 'assert'; import Holidays from 'date-holidays'; import Store from 'electron-store'; import { JSDOM } from 'jsdom'; -import sinon from 'sinon'; +import { stub } from 'sinon'; import path from 'path'; import { rootDir } from '../../js/app-config.mjs'; @@ -23,12 +23,9 @@ import { getUserPreferencesPromise, savePreferences, } from '../../js/user-preferences.mjs'; +import i18nTranslator from '../../renderer/i18n-translator.js'; -import { i18nTranslatorMock } from '../../renderer/i18n-translator.js'; -i18nTranslatorMock.mock('translatePage', sinon.stub().returnsThis()); -i18nTranslatorMock.mock('getTranslationInLanguageData', sinon.stub().returnsThis()); - const waiverStore = new Store({name: 'waived-workdays'}); const document = window.document; @@ -90,6 +87,9 @@ describe('Test Workday Waiver Window', function() { before(async() => { + stub(i18nTranslator, 'translatePage').returnsThis(); + stub(i18nTranslator, 'getTranslationInLanguageData').returnsThis(); + // Using dynamic imports because when the file is imported a $() callback is triggered and // methods must be mocked before-hand const file = await import('../../src/workday-waiver.js'); @@ -468,7 +468,7 @@ describe('Test Workday Waiver Window', function() $('#state').append($('').val(state).html(state)); const holidays = await getHolidays(); const holidaysLength = holidays.length; - const mockCallback = sinon.stub(); + const mockCallback = stub(); await iterateOnHolidays(mockCallback); assert.strictEqual(mockCallback.getCalls().length, holidaysLength); }); @@ -617,4 +617,10 @@ describe('Test Workday Waiver Window', function() assert.strictEqual(rowLength, 0); }); }); + + after(() => + { + i18nTranslator.translatePage.restore(); + i18nTranslator.getTranslationInLanguageData.restore(); + }); }); diff --git a/js/date-to-string-util.mjs b/js/date-to-string-util.mjs index 17530adf..1e1e6c64 100644 --- a/js/date-to-string-util.mjs +++ b/js/date-to-string-util.mjs @@ -1,6 +1,6 @@ 'use strict'; -import { getTranslationInLanguageData } from '../renderer/i18n-translator.js'; +import i18nTranslator from '../renderer/i18n-translator.js'; const dayAbbrs = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ]; const monthNames = [ 'january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december' ]; @@ -12,7 +12,7 @@ const monthNames = [ 'january', 'february', 'march', 'april', 'may', 'june', 'ju */ function getDayAbbr(languageData, dayIndex) { - return getTranslationInLanguageData(languageData, `$DateUtil.${dayAbbrs[dayIndex]}`); + return i18nTranslator.getTranslationInLanguageData(languageData, `$DateUtil.${dayAbbrs[dayIndex]}`); } @@ -23,7 +23,7 @@ function getDayAbbr(languageData, dayIndex) */ function getMonthName(languageData, monthIndex) { - return getTranslationInLanguageData(languageData, `$DateUtil.${monthNames[monthIndex]}`); + return i18nTranslator.getTranslationInLanguageData(languageData, `$DateUtil.${monthNames[monthIndex]}`); } export { diff --git a/js/demo-generator.mjs b/js/demo-generator.mjs index 62ee9a70..75a3365b 100644 --- a/js/demo-generator.mjs +++ b/js/demo-generator.mjs @@ -3,7 +3,7 @@ import Store from 'electron-store'; import { generateKey } from './date-db-formatter.js'; -import { hourMinToHourFormatted, sumTime } from './time-math.mjs'; +import TimeMath from './time-math.mjs'; /** * Returns a random integer between min (inclusive) and max (inclusive), rounding up to closest multiple of 5 @@ -27,7 +27,7 @@ function randomIntFromInterval(min, max) function randomTime(min, max) { const rand = randomIntFromInterval(min, max); - const timeStr = hourMinToHourFormatted(0/*hour*/, rand/*min*/); + const timeStr = TimeMath.hourMinToHourFormatted(0/*hour*/, rand/*min*/); const negative = randomIntFromInterval(0, 1) === 1; return `${negative ? '-' : ''}${timeStr}`; } @@ -55,10 +55,10 @@ function generateDemoInformation(dateFromStr, dateToStr, workingDays, usualTimes continue; } - const entry0 = sumTime(usualTimes[0], randomTime(0, 30)); - const entry1 = sumTime(usualTimes[1], randomTime(0, 15)); - const entry2 = sumTime(usualTimes[2], randomTime(0, 15)); - const entry3 = sumTime(usualTimes[3], randomTime(0, 30)); + const entry0 = TimeMath.sumTime(usualTimes[0], randomTime(0, 30)); + const entry1 = TimeMath.sumTime(usualTimes[1], randomTime(0, 15)); + const entry2 = TimeMath.sumTime(usualTimes[2], randomTime(0, 15)); + const entry3 = TimeMath.sumTime(usualTimes[3], randomTime(0, 30)); const entry = { values: [entry0, entry1, entry2, entry3] }; valuesToStore[generateKey(day.getFullYear(), day.getMonth(), day.getDate())] = entry; } diff --git a/js/import-export.mjs b/js/import-export.mjs index b0aae192..d6f2a420 100644 --- a/js/import-export.mjs +++ b/js/import-export.mjs @@ -6,8 +6,7 @@ import Store from 'electron-store'; import fs from 'fs'; import { generateKey } from './date-db-formatter.mjs'; -import { validateTime } from './time-math.mjs'; -import { MockClass } from '../__mocks__/Mock.mjs'; +import TimeMath from './time-math.mjs'; /** * Returns the database as an array of: @@ -56,112 +55,107 @@ function _getWaivedEntries() return output; } -function _exportDatabaseToFile(filename) -{ - let information = _getEntries(); - information = information.concat(_getWaivedEntries()); - try - { - fs.writeFileSync(filename, JSON.stringify(information, null,'\t'), 'utf-8'); - } - catch - { - return false; - } - return true; -} - function _validateDate(dateStr) { const date = new Date(dateStr); return date instanceof Date && !Number.isNaN(date.getTime()); } -function validEntry(entry) +class ImportExport { - if (entry.hasOwnProperty('type') && ['waived', 'flexible'].indexOf(entry.type) !== -1) + static validEntry(entry) { - const validatedDate = entry.hasOwnProperty('date') && _validateDate(entry.date); - let hasExpectedProperties; - let validatedTime = true; - if (entry.type === 'flexible') + if (entry.hasOwnProperty('type') && ['waived', 'flexible'].indexOf(entry.type) !== -1) { - hasExpectedProperties = entry.hasOwnProperty('values') && Array.isArray(entry.values) && entry.values.length > 0; - if (hasExpectedProperties) + const validatedDate = entry.hasOwnProperty('date') && _validateDate(entry.date); + let hasExpectedProperties; + let validatedTime = true; + if (entry.type === 'flexible') { - for (const value of entry.values) + hasExpectedProperties = entry.hasOwnProperty('values') && Array.isArray(entry.values) && entry.values.length > 0; + if (hasExpectedProperties) { - validatedTime &= (validateTime(value) || value === '--:--'); + for (const value of entry.values) + { + validatedTime &= (TimeMath.validateTime(value) || value === '--:--'); + } } } + else + { + hasExpectedProperties = entry.hasOwnProperty('data'); + validatedTime = entry.hasOwnProperty('hours') && TimeMath.validateTime(entry.hours); + } + if (hasExpectedProperties && validatedDate && validatedTime) + { + return true; + } } - else + return false; + } + + static exportDatabaseToFile(filename) + { + let information = _getEntries(); + information = information.concat(_getWaivedEntries()); + try { - hasExpectedProperties = entry.hasOwnProperty('data'); - validatedTime = entry.hasOwnProperty('hours') && validateTime(entry.hours); + fs.writeFileSync(filename, JSON.stringify(information, null,'\t'), 'utf-8'); } - if (hasExpectedProperties && validatedDate && validatedTime) + catch { - return true; + return false; } + return true; } - return false; -} -function _importDatabaseFromFile(filename) -{ - const calendarStore = new Store({name: 'flexible-store'}); - const waivedWorkdays = new Store({name: 'waived-workdays'}); - try + static importDatabaseFromFile(filename) { - const information = JSON.parse(fs.readFileSync(filename[0], 'utf-8')); - let failedEntries = 0; - const entries = {}; - const waiverEntries = {}; - for (let i = 0; i < information.length; ++i) + const calendarStore = new Store({name: 'flexible-store'}); + const waivedWorkdays = new Store({name: 'waived-workdays'}); + try { - const entry = information[i]; - if (!validEntry(entry)) + const information = JSON.parse(fs.readFileSync(filename[0], 'utf-8')); + let failedEntries = 0; + const entries = {}; + const waiverEntries = {}; + for (let i = 0; i < information.length; ++i) { - failedEntries += 1; - continue; - } - if (entry.type === 'waived') - { - waiverEntries[entry.date] = { 'reason' : entry.data, 'hours' : entry.hours }; + const entry = information[i]; + if (!ImportExport.validEntry(entry)) + { + failedEntries += 1; + continue; + } + if (entry.type === 'waived') + { + waiverEntries[entry.date] = { 'reason' : entry.data, 'hours' : entry.hours }; + } + else + { + assert(entry.type === 'flexible'); + const [year, month, day] = entry.date.split('-'); + //The main database uses a JS-based month index (0-11) + //So we need to adjust it from human month index (1-12) + const date = generateKey(year, (parseInt(month) - 1), day); + entries[date] = {values: entry.values}; + } } - else + + calendarStore.set(entries); + waivedWorkdays.set(waiverEntries); + + if (failedEntries !== 0) { - assert(entry.type === 'flexible'); - const [year, month, day] = entry.date.split('-'); - //The main database uses a JS-based month index (0-11) - //So we need to adjust it from human month index (1-12) - const date = generateKey(year, (parseInt(month) - 1), day); - entries[date] = {values: entry.values}; + return {'result': false, 'total': information.length, 'failed': failedEntries}; } } - - calendarStore.set(entries); - waivedWorkdays.set(waiverEntries); - - if (failedEntries !== 0) + catch { - return {'result': false, 'total': information.length, 'failed': failedEntries}; + return {'result': false, 'total': 0, 'failed': 0}; } + return {'result': true}; } - catch - { - return {'result': false, 'total': 0, 'failed': 0}; - } - return {'result': true}; } -// Enable mocking for some methods, export the mocked versions -const mocks = {'exportDatabaseToFile': _exportDatabaseToFile, 'importDatabaseFromFile': _importDatabaseFromFile}; -export const exportDatabaseToFile = (filename) => mocks['exportDatabaseToFile'](filename); -export const importDatabaseFromFile = (filename) => mocks['importDatabaseFromFile'](filename); -export const importExportMock = new MockClass(mocks); - -export { - validEntry, -}; +export default ImportExport; diff --git a/js/main-window.mjs b/js/main-window.mjs index 46b1161c..26de5abd 100644 --- a/js/main-window.mjs +++ b/js/main-window.mjs @@ -12,10 +12,10 @@ import { getMainMenuTemplate, getViewMenuTemplate } from './menus.mjs'; -import { createLeaveNotification } from './notification.mjs'; -import { checkForUpdates, shouldCheckForUpdates } from './update-manager.mjs'; +import Notification from './notification.mjs'; +import UpdateManager from './update-manager.mjs'; import { getDefaultWidthHeight, getUserPreferences, switchCalendarView } from './user-preferences.mjs'; -import { getCurrentTranslation } from '../src/configs/i18next.config.mjs'; +import i18NextConfig from '../src/configs/i18next.config.mjs'; // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. @@ -35,19 +35,19 @@ function createMenu() { const menu = Menu.buildFromTemplate([ { - label: getCurrentTranslation('$Menu.menu'), + label: i18NextConfig.getCurrentTranslation('$Menu.menu'), submenu: getMainMenuTemplate(mainWindow) }, { - label: getCurrentTranslation('$Menu.edit'), + label: i18NextConfig.getCurrentTranslation('$Menu.edit'), submenu: getEditMenuTemplate(mainWindow) }, { - label: getCurrentTranslation('$Menu.view'), + label: i18NextConfig.getCurrentTranslation('$Menu.view'), submenu: getViewMenuTemplate() }, { - label: getCurrentTranslation('$Menu.help'), + label: i18NextConfig.getCurrentTranslation('$Menu.help'), submenu: getHelpMenuTemplate() } ]); @@ -116,7 +116,7 @@ function createWindow() ipcMain.on('RECEIVE_LEAVE_BY', (event, element) => { - const notification = createLeaveNotification(element); + const notification = Notification.createLeaveNotification(element); if (notification) notification.show(); }); @@ -177,9 +177,9 @@ function createWindow() function triggerStartupDialogs() { - if (shouldCheckForUpdates()) + if (UpdateManager.shouldCheckForUpdates()) { - checkForUpdates(/*showUpToDateDialog=*/false); + UpdateManager.checkForUpdates(/*showUpToDateDialog=*/false); } } diff --git a/js/menus.mjs b/js/menus.mjs index e9e0ed7e..a5f439c9 100644 --- a/js/menus.mjs +++ b/js/menus.mjs @@ -6,28 +6,28 @@ import path from 'path'; import { appConfig, getDetails, rootDir } from './app-config.mjs'; import { getCurrentDateTimeStr } from './date-aux.mjs'; -import { importDatabaseFromFile, exportDatabaseToFile } from './import-export.mjs'; -import { createNotification } from './notification.mjs'; +import ImportExport from './import-export.mjs'; +import Notification from './notification.mjs'; import { getSavedPreferences } from './saved-preferences.mjs'; -import { checkForUpdates } from './update-manager.mjs'; +import UpdateManager from './update-manager.mjs'; import { savePreferences } from './user-preferences.mjs'; -import { openWaiverManagerWindow, getDialogCoordinates } from './windows.mjs'; -import { getCurrentTranslation } from '../src/configs/i18next.config.mjs'; +import Windows from './windows.mjs'; +import i18NextConfig from '../src/configs/i18next.config.mjs'; function getMainMenuTemplate(mainWindow) { return [ { - label: getCurrentTranslation('$Menu.workday-waiver-manager'), + label: i18NextConfig.getCurrentTranslation('$Menu.workday-waiver-manager'), id: 'workday-waiver-manager', click(item, window, event) { - openWaiverManagerWindow(mainWindow, event); + Windows.openWaiverManagerWindow(mainWindow, event); } }, { type: 'separator' }, { - label: getCurrentTranslation('$Menu.exit'), + label: i18NextConfig.getCurrentTranslation('$Menu.exit'), accelerator: appConfig.macOS ? 'CommandOrControl+Q' : 'Control+Q', click() { @@ -41,29 +41,29 @@ function getContextMenuTemplate(mainWindow) { return [ { - label: getCurrentTranslation('$Menu.punch-time'), + label: i18NextConfig.getCurrentTranslation('$Menu.punch-time'), click: function() { const now = new Date(); mainWindow.webContents.send('PUNCH_DATE'); // Slice keeps "HH:MM" part of "HH:MM:SS GMT+HHMM (GMT+HH:MM)" time string - createNotification( - `${getCurrentTranslation( + Notification.createNotification( + `${i18NextConfig.getCurrentTranslation( '$Menu.punched-time' )} ${now.toTimeString().slice(0, 5)}` ).show(); } }, { - label: getCurrentTranslation('$Menu.show-app'), + label: i18NextConfig.getCurrentTranslation('$Menu.show-app'), click: function() { mainWindow.show(); } }, { - label: getCurrentTranslation('$Menu.quit'), + label: i18NextConfig.getCurrentTranslation('$Menu.quit'), click: function() { app.quit(); @@ -76,15 +76,15 @@ function getDockMenuTemplate(mainWindow) { return [ { - label: getCurrentTranslation('$Menu.punch-time'), + label: i18NextConfig.getCurrentTranslation('$Menu.punch-time'), click: function() { const now = new Date(); mainWindow.webContents.send('PUNCH_DATE'); // Slice keeps "HH:MM" part of "HH:MM:SS GMT+HHMM (GMT+HH:MM)" time string - createNotification( - `${getCurrentTranslation( + Notification.createNotification( + `${i18NextConfig.getCurrentTranslation( '$Menu.punched-time' )} ${now.toTimeString().slice(0, 5)}` ).show(); @@ -97,28 +97,28 @@ function getEditMenuTemplate(mainWindow) { return [ { - label: getCurrentTranslation('$Menu.cut'), + label: i18NextConfig.getCurrentTranslation('$Menu.cut'), accelerator: 'Command+X', selector: 'cut:' }, { - label: getCurrentTranslation('$Menu.copy'), + label: i18NextConfig.getCurrentTranslation('$Menu.copy'), accelerator: 'Command+C', selector: 'copy:' }, { - label: getCurrentTranslation('$Menu.paste'), + label: i18NextConfig.getCurrentTranslation('$Menu.paste'), accelerator: 'Command+V', selector: 'paste:' }, { - label: getCurrentTranslation('$Menu.select-all'), + label: i18NextConfig.getCurrentTranslation('$Menu.select-all'), accelerator: 'Command+A', selector: 'selectAll:' }, { type: 'separator' }, { - label: getCurrentTranslation('$Menu.preferences'), + label: i18NextConfig.getCurrentTranslation('$Menu.preferences'), accelerator: appConfig.macOS ? 'Command+,' : 'Control+,', click() { @@ -129,7 +129,7 @@ function getEditMenuTemplate(mainWindow) } const htmlPath = path.join('file://', rootDir, 'src/preferences.html'); - const dialogCoordinates = getDialogCoordinates(550, 620, mainWindow); + const dialogCoordinates = Windows.getDialogCoordinates(550, 620, mainWindow); global.prefWindow = new BrowserWindow({ width: 550, height: 620, minWidth: 480, @@ -167,18 +167,18 @@ function getEditMenuTemplate(mainWindow) }, { type: 'separator' }, { - label: getCurrentTranslation('$Menu.export-database'), + label: i18NextConfig.getCurrentTranslation('$Menu.export-database'), click() { const options = { - title: getCurrentTranslation('$Menu.export-db-to-file'), + title: i18NextConfig.getCurrentTranslation('$Menu.export-db-to-file'), defaultPath: `time_to_leave_${getCurrentDateTimeStr()}`, - buttonLabel: getCurrentTranslation('$Menu.export'), + buttonLabel: i18NextConfig.getCurrentTranslation('$Menu.export'), filters: [ { name: '.ttldb', extensions: ['ttldb'] }, { - name: getCurrentTranslation('$Menu.all-files'), + name: i18NextConfig.getCurrentTranslation('$Menu.all-files'), extensions: ['*'] } ] @@ -186,29 +186,29 @@ function getEditMenuTemplate(mainWindow) const response = dialog.showSaveDialogSync(options); if (response) { - exportDatabaseToFile(response); + ImportExport.exportDatabaseToFile(response); dialog.showMessageBox(BrowserWindow.getFocusedWindow(), { title: 'Time to Leave', - message: getCurrentTranslation('$Menu.database-export'), + message: i18NextConfig.getCurrentTranslation('$Menu.database-export'), type: 'info', icon: appConfig.iconpath, - detail: getCurrentTranslation('$Menu.database-was-exported') + detail: i18NextConfig.getCurrentTranslation('$Menu.database-was-exported') }); } } }, { - label: getCurrentTranslation('$Menu.import-database'), + label: i18NextConfig.getCurrentTranslation('$Menu.import-database'), click() { const options = { - title: getCurrentTranslation('$Menu.import-db-from-file'), - buttonLabel: getCurrentTranslation('$Menu.import'), + title: i18NextConfig.getCurrentTranslation('$Menu.import-db-from-file'), + buttonLabel: i18NextConfig.getCurrentTranslation('$Menu.import'), filters: [ { name: '.ttldb', extensions: ['ttldb'] }, { - name: getCurrentTranslation('$Menu.all-files'), + name: i18NextConfig.getCurrentTranslation('$Menu.all-files'), extensions: ['*'] } ] @@ -219,12 +219,12 @@ function getEditMenuTemplate(mainWindow) const options = { type: 'question', buttons: [ - getCurrentTranslation('$Menu.yes-please'), - getCurrentTranslation('$Menu.no-thanks') + i18NextConfig.getCurrentTranslation('$Menu.yes-please'), + i18NextConfig.getCurrentTranslation('$Menu.no-thanks') ], defaultId: 2, - title: getCurrentTranslation('$Menu.import-database'), - message: getCurrentTranslation('$Menu.confirm-import-db') + title: i18NextConfig.getCurrentTranslation('$Menu.import-database'), + message: i18NextConfig.getCurrentTranslation('$Menu.confirm-import-db') }; const confirmation = dialog.showMessageBoxSync( @@ -233,28 +233,28 @@ function getEditMenuTemplate(mainWindow) ); if (confirmation === /*Yes*/ 0) { - const importResult = importDatabaseFromFile(response); + const importResult = ImportExport.importDatabaseFromFile(response); // Reload only the calendar itself to avoid a flash mainWindow.webContents.send('RELOAD_CALENDAR'); if (importResult['result']) { dialog.showMessageBox(BrowserWindow.getFocusedWindow(), { title: 'Time to Leave', - message: getCurrentTranslation('$Menu.database-imported'), + message: i18NextConfig.getCurrentTranslation('$Menu.database-imported'), type: 'info', icon: appConfig.iconpath, - detail: getCurrentTranslation('$Menu.import-successful') + detail: i18NextConfig.getCurrentTranslation('$Menu.import-successful') }); } else if (importResult['failed'] !== 0) { const message = `${importResult['failed']}/${ importResult['total'] - } ${getCurrentTranslation('$Menu.could-not-be-loaded')}`; + } ${i18NextConfig.getCurrentTranslation('$Menu.could-not-be-loaded')}`; dialog.showMessageBoxSync({ icon: appConfig.iconpath, type: 'warning', - title: getCurrentTranslation('$Menu.failed-entries'), + title: i18NextConfig.getCurrentTranslation('$Menu.failed-entries'), message: message }); } @@ -263,8 +263,8 @@ function getEditMenuTemplate(mainWindow) dialog.showMessageBoxSync({ icon: appConfig.iconpath, type: 'warning', - title: getCurrentTranslation('$Menu.failed-entries'), - message: getCurrentTranslation('$Menu.something-went-wrong') + title: i18NextConfig.getCurrentTranslation('$Menu.failed-entries'), + message: i18NextConfig.getCurrentTranslation('$Menu.something-went-wrong') }); } } @@ -272,19 +272,19 @@ function getEditMenuTemplate(mainWindow) } }, { - label: getCurrentTranslation('$Menu.clear-database'), + label: i18NextConfig.getCurrentTranslation('$Menu.clear-database'), click() { const options = { type: 'question', buttons: [ - getCurrentTranslation('$Menu.cancel'), - getCurrentTranslation('$Menu.yes-please'), - getCurrentTranslation('$Menu.no-thanks') + i18NextConfig.getCurrentTranslation('$Menu.cancel'), + i18NextConfig.getCurrentTranslation('$Menu.yes-please'), + i18NextConfig.getCurrentTranslation('$Menu.no-thanks') ], defaultId: 2, - title: getCurrentTranslation('$Menu.clear-database'), - message: getCurrentTranslation('$Menu.confirm-clear-all-data') + title: i18NextConfig.getCurrentTranslation('$Menu.clear-database'), + message: i18NextConfig.getCurrentTranslation('$Menu.confirm-clear-all-data') }; const response = dialog.showMessageBoxSync( @@ -304,10 +304,10 @@ function getEditMenuTemplate(mainWindow) mainWindow.webContents.send('RELOAD_CALENDAR'); dialog.showMessageBox(BrowserWindow.getFocusedWindow(), { title: 'Time to Leave', - message: getCurrentTranslation('$Menu.clear-database'), + message: i18NextConfig.getCurrentTranslation('$Menu.clear-database'), type: 'info', icon: appConfig.iconpath, - detail: `\n${getCurrentTranslation('$Menu.all-clear')}` + detail: `\n${i18NextConfig.getCurrentTranslation('$Menu.all-clear')}` }); } } @@ -319,7 +319,7 @@ function getViewMenuTemplate() { return [ { - label: getCurrentTranslation('$Menu.reload'), + label: i18NextConfig.getCurrentTranslation('$Menu.reload'), accelerator: 'CommandOrControl+R', click() { @@ -327,7 +327,7 @@ function getViewMenuTemplate() } }, { - label: getCurrentTranslation('$Menu.toggle-dev-tools'), + label: i18NextConfig.getCurrentTranslation('$Menu.toggle-dev-tools'), accelerator: appConfig.macOS ? 'Command+Alt+I' : 'Control+Shift+I', click() { @@ -341,21 +341,21 @@ function getHelpMenuTemplate() { return [ { - label: getCurrentTranslation('$Menu.ttl-github'), + label: i18NextConfig.getCurrentTranslation('$Menu.ttl-github'), click() { shell.openExternal('https://github.com/TTLApp/time-to-leave'); } }, { - label: getCurrentTranslation('$Menu.check-for-updates'), + label: i18NextConfig.getCurrentTranslation('$Menu.check-for-updates'), click() { - checkForUpdates(/*showUpToDateDialog=*/ true); + UpdateManager.checkForUpdates(/*showUpToDateDialog=*/ true); } }, { - label: getCurrentTranslation('$Menu.send-feedback'), + label: i18NextConfig.getCurrentTranslation('$Menu.send-feedback'), click() { shell.openExternal( @@ -367,7 +367,7 @@ function getHelpMenuTemplate() type: 'separator' }, { - label: getCurrentTranslation('$Menu.about'), + label: i18NextConfig.getCurrentTranslation('$Menu.about'), click() { const detail = getDetails(); @@ -379,8 +379,8 @@ function getHelpMenuTemplate() icon: appConfig.iconpath, detail: `\n${detail}`, buttons: [ - getCurrentTranslation('$Menu.copy'), - getCurrentTranslation('$Menu.ok') + i18NextConfig.getCurrentTranslation('$Menu.copy'), + i18NextConfig.getCurrentTranslation('$Menu.ok') ], noLink: true }) diff --git a/js/notification.mjs b/js/notification.mjs index 77ca3808..c47c42b8 100644 --- a/js/notification.mjs +++ b/js/notification.mjs @@ -5,128 +5,121 @@ import { app, Notification as ElectronNotification } from 'electron'; import { rootDir } from './app-config.mjs'; import { getDateStr } from './date-aux.mjs'; -import { hourToMinutes, subtractTime, validateTime } from './time-math.mjs'; +import TimeMath from './time-math.mjs'; import { getNotificationsInterval, notificationIsEnabled, repetitionIsEnabled } from './user-preferences.mjs'; -import { getCurrentTranslation } from '../src/configs/i18next.config.mjs'; -import { MockClass } from '../__mocks__/Mock.mjs'; +import i18NextConfig from '../src/configs/i18next.config.mjs'; let dismissToday = null; -function _createNotification(msg, actions = []) +class Notification { - const appPath = process.env.NODE_ENV === 'production' - ? `${process.resourcesPath}/app` - : rootDir; - let notification; - if (process.platform === 'win32') + static createNotification(msg, actions = []) { - notification = new ElectronNotification({ toastXml: ` - - - - - Time to Leave - ${msg} - - - - ${actions.map(action => ``)} - - ` - }); + const appPath = process.env.NODE_ENV === 'production' + ? `${process.resourcesPath}/app` + : rootDir; + let notification; + if (process.platform === 'win32') + { + notification = new ElectronNotification({ toastXml: ` + + + + + Time to Leave + ${msg} + + + + ${actions.map(action => ``)} + + ` + }); - } - else - { - notification = new ElectronNotification({ - title: 'Time to Leave', - body: msg, - icon: path.join(appPath, 'assets/ttl.png'), - sound: true, - actions - }); + } + else + { + notification = new ElectronNotification({ + title: 'Time to Leave', + body: msg, + icon: path.join(appPath, 'assets/ttl.png'), + sound: true, + actions + }); + } + notification.addListener('click', () => + { + app.emit('activate'); + }); + return notification; } - notification.addListener('click', () => - { - app.emit('activate'); - }); - return notification; -} -/* - * Notify user if it's time to leave - */ -function _createLeaveNotification(timeToLeave) -{ - const now = new Date(); - const dateToday = getDateStr(now); - const skipNotify = dismissToday === dateToday; - - if (!notificationIsEnabled() || !timeToLeave || skipNotify) + /* + * Notify user if it's time to leave + */ + static createLeaveNotification(timeToLeave) { - return false; - } + const now = new Date(); + const dateToday = getDateStr(now); + const skipNotify = dismissToday === dateToday; - if (validateTime(timeToLeave)) - { - /** - * How many minutes should pass before the Time-To-Leave notification should be presented again. - * @type {number} Minutes post the clockout time - */ - const notificationInterval = getNotificationsInterval(); - const curTime = String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0'); + if (!notificationIsEnabled() || !timeToLeave || skipNotify) + { + return false; + } - // Let check if it's past the time to leave, and the minutes line up with the interval to check - const minutesDiff = hourToMinutes(subtractTime(timeToLeave, curTime)); - const isRepeatingInterval = curTime > timeToLeave && (minutesDiff % notificationInterval === 0); - if (curTime === timeToLeave || (isRepeatingInterval && repetitionIsEnabled())) + if (TimeMath.validateTime(timeToLeave)) { - const dismissForTodayText = getCurrentTranslation('$Notification.dismiss-for-today'); - const dismissBtn = {type: 'button', text: dismissForTodayText, action: 'dismiss', title: 'dismiss'}; - return createNotification(getCurrentTranslation('$Notification.time-to-leave'), [dismissBtn]) - .addListener('action', (response) => - { - // Actions are only supported on macOS - if ( response && dismissBtn.title.toLowerCase() === response.toLowerCase()) + /** + * How many minutes should pass before the Time-To-Leave notification should be presented again. + * @type {number} Minutes post the clockout time + */ + const notificationInterval = getNotificationsInterval(); + const curTime = String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0'); + + // Let check if it's past the time to leave, and the minutes line up with the interval to check + const minutesDiff = TimeMath.hourToMinutes(TimeMath.subtractTime(timeToLeave, curTime)); + const isRepeatingInterval = curTime > timeToLeave && (minutesDiff % notificationInterval === 0); + if (curTime === timeToLeave || (isRepeatingInterval && repetitionIsEnabled())) + { + const dismissForTodayText = i18NextConfig.getCurrentTranslation('$Notification.dismiss-for-today'); + const dismissBtn = {type: 'button', text: dismissForTodayText, action: 'dismiss', title: 'dismiss'}; + return Notification.createNotification(i18NextConfig.getCurrentTranslation('$Notification.time-to-leave'), [dismissBtn]) + .addListener('action', (response) => { + // Actions are only supported on macOS + if ( response && dismissBtn.title.toLowerCase() === response.toLowerCase()) + { + dismissToday = dateToday; + } + }).addListener('close', () => + { + // We'll assume that if someone closes the notification they're + // dismissing the notifications dismissToday = dateToday; - } - }).addListener('close', () => - { - // We'll assume that if someone closes the notification they're - // dismissing the notifications - dismissToday = dateToday; - }); + }); + } } + return false; } - return false; -} -/** - * Test related function to force a dismiss value - * @param {String} dismiss Dismiss value in HH:MM format - */ -function updateDismiss(dismiss) -{ - dismissToday = dismiss; -} + /** + * Test related function to force a dismiss value + * @param {String} dismiss Dismiss value in HH:MM format + */ + static updateDismiss(dismiss) + { + dismissToday = dismiss; + } -/** - * Test related function to get the dismiss value - */ -function getDismiss() -{ - return dismissToday; + /** + * Test related function to get the dismiss value + */ + static getDismiss() + { + return dismissToday; + } } -// Enable mocking for some methods, export the mocked versions -const mocks = {'createNotification': _createNotification, 'createLeaveNotification': _createLeaveNotification}; -export const createNotification = (msg, actions = []) => mocks['createNotification'](msg, actions); -export const createLeaveNotification = (timeToLeave) => mocks['createLeaveNotification'](timeToLeave); -export const notificationMock = new MockClass(mocks); - -export { - getDismiss, - updateDismiss -}; +export default Notification; diff --git a/js/saved-preferences.mjs b/js/saved-preferences.mjs index 1c8cff08..60339537 100644 --- a/js/saved-preferences.mjs +++ b/js/saved-preferences.mjs @@ -2,7 +2,7 @@ import { app, ipcMain } from 'electron'; -import { changeLanguage } from '../src/configs/i18next.config.mjs'; +import i18NextConfig from '../src/configs/i18next.config.mjs'; let savedPreferences = null; @@ -17,7 +17,7 @@ ipcMain.on('PREFERENCE_SAVE_DATA_NEEDED', (event, preferences) => app.setLoginItemSettings({ openAtLogin: preferences['start-at-login'] }); - changeLanguage(preferences.language).catch((err) => + i18NextConfig.changeLanguage(preferences.language).catch((err) => { if (err) return console.log('something went wrong loading', err); }); diff --git a/js/time-balance.mjs b/js/time-balance.mjs index 3f69aca2..6e24943a 100644 --- a/js/time-balance.mjs +++ b/js/time-balance.mjs @@ -3,38 +3,13 @@ import Store from 'electron-store'; import { getDateStr } from './date-aux.mjs'; -import { subtractTime, sumTime } from './time-math.mjs'; +import TimeMath from './time-math.mjs'; import { getUserPreferences, showDay } from './user-preferences.mjs'; -import { MockClass } from '../__mocks__/Mock.mjs'; // Global values for calendar const calendarStore = new Store({ name: 'flexible-store' }); const waivedWorkdays = new Store({ name: 'waived-workdays' }); -function getFirstInputInDb() -{ - const inputs = []; - const startDateStr = _getOverallBalanceStartDate(); - const [startYear, startMonth, startDay] = startDateStr.split('-'); - const startDate = new Date(startYear, startMonth - 1, startDay); - - for (const value of calendarStore) - { - const [year, month, day] = value[0].split('-'); - if (new Date(year, month, day) >= startDate) - { - inputs.push(value[0]); - } - } - inputs.sort(function(a, b) - { - const [aYear, aMonth, aDay] = a.split('-'); - const [bYear, bMonth, bDay] = b.split('-'); - return new Date(aYear, aMonth, aDay) - new Date(bYear, bMonth, bDay); - }); - return inputs.length ? inputs[0] : ''; -} - /** * @param {string} dbKey given key of the db */ @@ -87,8 +62,8 @@ function _getDayTotal(values) { for (let i = 0; i < values.length; i += 2) { - const difference = subtractTime(values[i], values[i + 1]); - dayTotal = sumTime(dayTotal, difference); + const difference = TimeMath.subtractTime(values[i], values[i + 1]); + dayTotal = TimeMath.sumTime(dayTotal, difference); if (values[i] >= values[i + 1]) { timesAreProgressing = false; @@ -151,63 +126,82 @@ function _getDayTotalsFromStores(firstDate, limitDate) return totals; } -/** -* Computation of all time balance, including limitDay. -* @param {Date} limitDate -*/ -async function computeAllTimeBalanceUntil(limitDate) +class TimeBalance { - const firstInput = getFirstInputInDb(); - if (firstInput === '') + static getFirstInputInDb() { - return '00:00'; - } - const [firstYear, firstMonth, firstDay] = firstInput.split('-'); - const firstDate = new Date(firstYear, firstMonth, firstDay); + const inputs = []; + const startDateStr = _getOverallBalanceStartDate(); + const [startYear, startMonth, startDay] = startDateStr.split('-'); + const startDate = new Date(startYear, startMonth - 1, startDay); - const totals = _getDayTotalsFromStores(firstDate, limitDate); + for (const value of calendarStore) + { + const [year, month, day] = value[0].split('-'); + if (new Date(year, month, day) >= startDate) + { + inputs.push(value[0]); + } + } + inputs.sort(function(a, b) + { + const [aYear, aMonth, aDay] = a.split('-'); + const [bYear, bMonth, bDay] = b.split('-'); + return new Date(aYear, aMonth, aDay) - new Date(bYear, bMonth, bDay); + }); + return inputs.length ? inputs[0] : ''; + } - const preferences = getUserPreferences(); - const hoursPerDay = _getHoursPerDay(); - let allTimeTotal = '00:00'; - const date = new Date(firstDate); - const limitDateStr = getDateStr(limitDate); - let dateStr = getDateStr(date); - while (dateStr !== limitDateStr && limitDate > date) + /** + * Computation of all time balance, including limitDay. + * @param {Date} limitDate + */ + static async computeAllTimeBalanceUntil(limitDate) { - if (showDay(date.getFullYear(), date.getMonth(), date.getDate(), preferences)) + const firstInput = TimeBalance.getFirstInputInDb(); + if (firstInput === '') { - const dayTotal = dateStr in totals ? totals[dateStr] : '00:00'; - const dayBalance = subtractTime(hoursPerDay, dayTotal); - allTimeTotal = sumTime(dayBalance, allTimeTotal); + return '00:00'; + } + const [firstYear, firstMonth, firstDay] = firstInput.split('-'); + const firstDate = new Date(firstYear, firstMonth, firstDay); + + const totals = _getDayTotalsFromStores(firstDate, limitDate); + + const preferences = getUserPreferences(); + const hoursPerDay = _getHoursPerDay(); + let allTimeTotal = '00:00'; + const date = new Date(firstDate); + const limitDateStr = getDateStr(limitDate); + let dateStr = getDateStr(date); + while (dateStr !== limitDateStr && limitDate > date) + { + if (showDay(date.getFullYear(), date.getMonth(), date.getDate(), preferences)) + { + const dayTotal = dateStr in totals ? totals[dateStr] : '00:00'; + const dayBalance = TimeMath.subtractTime(hoursPerDay, dayTotal); + allTimeTotal = TimeMath.sumTime(dayBalance, allTimeTotal); + } + date.setDate(date.getDate() + 1); + dateStr = getDateStr(date); } - date.setDate(date.getDate() + 1); - dateStr = getDateStr(date); + return allTimeTotal; } - return allTimeTotal; -} -/** -* Computes all time balance using an async promise. -* @param {Date} limitDate -*/ -async function _computeAllTimeBalanceUntilAsync(limitDate) -{ - return new Promise(resolve => + /** + * Computes all time balance using an async promise. + * @param {Date} limitDate + */ + static async computeAllTimeBalanceUntilAsync(limitDate) { - setTimeout(() => + return new Promise(resolve => { - resolve(computeAllTimeBalanceUntil(limitDate)); - }, 1); - }); + setTimeout(() => + { + resolve(TimeBalance.computeAllTimeBalanceUntil(limitDate)); + }, 1); + }); + } } -// Enable mocking for some methods, export the mocked versions -const mocks = {'computeAllTimeBalanceUntilAsync': _computeAllTimeBalanceUntilAsync}; -export const computeAllTimeBalanceUntilAsync = (limitDate) => mocks['computeAllTimeBalanceUntilAsync'](limitDate); -export const timeBalanceMock = new MockClass(mocks); - -export { - computeAllTimeBalanceUntil, - getFirstInputInDb, -}; +export default TimeBalance; diff --git a/js/time-math.mjs b/js/time-math.mjs index bf18eb71..eaa84c54 100644 --- a/js/time-math.mjs +++ b/js/time-math.mjs @@ -1,166 +1,152 @@ 'use strict'; -import { MockClass } from '../__mocks__/Mock.mjs'; - -/** - * Formats hour, min into string HH:MM - */ -function hourMinToHourFormatted(hours, minutes) +class TimeMath { - const paddingHour = hours < 10 ? '0' : ''; - const paddingMin = minutes < 10 ? '0' : ''; - return paddingHour + hours + - ':' + - paddingMin + minutes; -} - -/** - * Determines if a time string holds a negative value - */ -function _isNegative(str) -{ - return str[0] === '-'; -} + /** + * Formats hour, min into string HH:MM + */ + static hourMinToHourFormatted(hours, minutes) + { + const paddingHour = hours < 10 ? '0' : ''; + const paddingMin = minutes < 10 ? '0' : ''; + return paddingHour + hours + + ':' + + paddingMin + minutes; + } -/** - * Converts hour to min. - * Hours must be formated as HH:MM - */ -function hourToMinutes(time) -{ - const st = time.split(':'); - const isNeg = isNegative(time); - st[0] = isNeg ? st[0].substr(1) : st[0]; + /** + * Determines if a time string holds a negative value + */ + static isNegative(str) + { + return str[0] === '-'; + } - let min = Number(st[1]) + (Number(st[0]) * 60); - if (isNeg) + /** + * Converts hour to min. + * Hours must be formated as HH:MM + */ + static hourToMinutes(time) { - min = min * -1; + const st = time.split(':'); + const isNeg = TimeMath.isNegative(time); + st[0] = isNeg ? st[0].substr(1) : st[0]; + + let min = Number(st[1]) + (Number(st[0]) * 60); + if (isNeg) + { + min = min * -1; + } + return min; } - return min; -} -/** - * Formats a given amount of minutes into string HH:MM - */ -function minutesToHourFormatted(min) -{ - const signStr = min < 0 ? '-' : ''; - if (min < 0) + /** + * Formats a given amount of minutes into string HH:MM + */ + static minutesToHourFormatted(min) { - min = Math.abs(min); + const signStr = min < 0 ? '-' : ''; + if (min < 0) + { + min = Math.abs(min); + } + const hours = Math.floor(min / 60); + const minutes = Math.floor(min - (hours * 60)); + return signStr + TimeMath.hourMinToHourFormatted(hours, minutes); } - const hours = Math.floor(min / 60); - const minutes = Math.floor(min - (hours * 60)); - return signStr + hourMinToHourFormatted(hours, minutes); -} -/** - * Subtracts time first from second (t2 - t1) - * Time should be formated as HH:MM - */ -function subtractTime(t1, t2) -{ - const diffMin = hourToMinutes(t2) - hourToMinutes(t1); - return minutesToHourFormatted(diffMin); -} + /** + * Subtracts time first from second (t2 - t1) + * Time should be formated as HH:MM + */ + static subtractTime(t1, t2) + { + const diffMin = TimeMath.hourToMinutes(t2) - TimeMath.hourToMinutes(t1); + return TimeMath.minutesToHourFormatted(diffMin); + } -/** - * Multiplies t * n - * Time should be formated as HH:MM - */ -function multiplyTime(t, n) -{ - let totalMin = hourToMinutes(t); - totalMin = totalMin * n; - return minutesToHourFormatted(totalMin); -} + /** + * Multiplies t * n + * Time should be formated as HH:MM + */ + static multiplyTime(t, n) + { + let totalMin = TimeMath.hourToMinutes(t); + totalMin = totalMin * n; + return TimeMath.minutesToHourFormatted(totalMin); + } -/** - * Sums time first to second (t1 + t2) - * Time should be formated as HH:MM - */ -function sumTime(t1, t2) -{ - const sumMin = hourToMinutes(t2) + hourToMinutes(t1); - return minutesToHourFormatted(sumMin); -} + /** + * Sums time first to second (t1 + t2) + * Time should be formated as HH:MM + */ + static sumTime(t1, t2) + { + const sumMin = TimeMath.hourToMinutes(t2) + TimeMath.hourToMinutes(t1); + return TimeMath.minutesToHourFormatted(sumMin); + } -/** - * Validates that a string is a valid time, following the format of HH:MM - * @returns true if it's valid - */ -function validateTime(time) -{ - const re = new RegExp('^-?(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$'); - return re.test(time); -} + /** + * Validates that a string is a valid time, following the format of HH:MM + * @returns true if it's valid + */ + static validateTime(time) + { + const re = new RegExp('^-?(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$'); + return re.test(time); + } -/** - * Get a difference between two dates. - * date1, or date2 should be javascript Date instance. - * @return Number - */ -function diffDays(date1, date2) -{ - const diffTime = date2 - date1; - return Math.ceil(diffTime / (1000 * 60 * 60 * 24)); -} + /** + * Get a difference between two dates. + * date1, or date2 should be javascript Date instance. + * @return Number + */ + static diffDays(date1, date2) + { + const diffTime = date2 - date1; + return Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + } -/** - * Check if the day is valid on its month - * @param {number} dayOfMonth Date of the month from 0 to 31 - * @param {number} month Month from 0 to 11 - * @returns {boolean} - */ -function isValidDayOfMonth(dayOfMonth, month) -{ - const validDates = { - 0: (day) => day >= 1 && day <= 31, - 1: (day) => day >= 1 && day <= 29, - 2: (day) => day >= 1 && day <= 31, - 3: (day) => day >= 1 && day <= 30, - 4: (day) => day >= 1 && day <= 31, - 5: (day) => day >= 1 && day <= 30, - 6: (day) => day >= 1 && day <= 31, - 7: (day) => day >= 1 && day <= 31, - 8: (day) => day >= 1 && day <= 30, - 9: (day) => day >= 1 && day <= 31, - 10: (day) => day >= 1 && day <= 30, - 11: (day) => day >= 1 && day <= 31 - }; - return validDates[month](dayOfMonth); -} + /** + * Check if the day is valid on its month + * @param {number} dayOfMonth Date of the month from 0 to 31 + * @param {number} month Month from 0 to 11 + * @returns {boolean} + */ + static isValidDayOfMonth(dayOfMonth, month) + { + const validDates = { + 0: (day) => day >= 1 && day <= 31, + 1: (day) => day >= 1 && day <= 29, + 2: (day) => day >= 1 && day <= 31, + 3: (day) => day >= 1 && day <= 30, + 4: (day) => day >= 1 && day <= 31, + 5: (day) => day >= 1 && day <= 30, + 6: (day) => day >= 1 && day <= 31, + 7: (day) => day >= 1 && day <= 31, + 8: (day) => day >= 1 && day <= 30, + 9: (day) => day >= 1 && day <= 31, + 10: (day) => day >= 1 && day <= 30, + 11: (day) => day >= 1 && day <= 31 + }; + return validDates[month](dayOfMonth); + } -/** - * Check if a date has a valid YYYY-MM-DD format - * @param {string} date String date to validate - * @returns {boolean} - */ -function validateDate(date) -{ - const re = new RegExp('(1|2)[0-9]{3}-(0[0-9]{1}|1[0-1]{1})-(0[0-9]{1}|1[0-9]{1}|2[0-9]{1}|3[0-1]{1})$'); - if (re.test(date)) + /** + * Check if a date has a valid YYYY-MM-DD format + * @param {string} date String date to validate + * @returns {boolean} + */ + static validateDate(date) { - const [, month, day] = date.split('-').map(parseFloat); - return isValidDayOfMonth(day, month); + const re = new RegExp('(1|2)[0-9]{3}-(0[0-9]{1}|1[0-1]{1})-(0[0-9]{1}|1[0-9]{1}|2[0-9]{1}|3[0-1]{1})$'); + if (re.test(date)) + { + const [, month, day] = date.split('-').map(parseFloat); + return TimeMath.isValidDayOfMonth(day, month); + } + return false; } - return false; } -// Enable mocking for some methods, export the mocked versions -const mocks = {'isNegative': _isNegative}; -export const isNegative = (str) => mocks['isNegative'](str); -export const timeMathMock = new MockClass(mocks); - -export { - diffDays, - hourMinToHourFormatted, - hourToMinutes, - minutesToHourFormatted, - multiplyTime, - subtractTime, - sumTime, - validateDate, - validateTime, -}; +export default TimeMath; diff --git a/js/update-manager.mjs b/js/update-manager.mjs index af6429c2..c6fdff4a 100644 --- a/js/update-manager.mjs +++ b/js/update-manager.mjs @@ -5,83 +5,81 @@ import Store from 'electron-store'; import isOnline from 'is-online'; import { getDateStr } from './date-aux.mjs'; -import { getCurrentTranslation } from '../src/configs/i18next.config.mjs'; -import { MockClass } from '../__mocks__/Mock.mjs'; +import i18NextConfig from '../src/configs/i18next.config.mjs'; -function _shouldCheckForUpdates() +class UpdateManager { - const store = new Store(); - const lastChecked = store.get('update-remind-me-after'); - const today = new Date(), - todayDate = getDateStr(today); - return !lastChecked || todayDate > lastChecked; -} - -async function _checkForUpdates(showUpToDateDialog) -{ - const online = await isOnline(); - if (!online) + static shouldCheckForUpdates() { - return; + const store = new Store(); + const lastChecked = store.get('update-remind-me-after'); + const today = new Date(), + todayDate = getDateStr(today); + return !lastChecked || todayDate > lastChecked; } - const request = net.request('https://api.github.com/repos/TTLApp/time-to-leave/releases/latest'); - request.on('response', (response) => + static async checkForUpdates(showUpToDateDialog) { - response.on('data', (chunk) => + const online = await isOnline(); + if (!online) + { + return; + } + + const request = net.request('https://api.github.com/repos/TTLApp/time-to-leave/releases/latest'); + request.on('response', (response) => { - const result = `${chunk}`; - const re = new RegExp('.*(tag_name).*', 'g'); - const matches = result.matchAll(re); - for (const match of matches) + response.on('data', (chunk) => { - const res = match[0].replace(/.*v.(\d+\.\d+\.\d+).*/g, '$1'); - if (app.getVersion() < res) + const result = `${chunk}`; + const re = new RegExp('.*(tag_name).*', 'g'); + const matches = result.matchAll(re); + for (const match of matches) { - const options = { - type: 'question', - buttons: [ - getCurrentTranslation('$UpdateManager.dismissBtn'), - getCurrentTranslation('$UpdateManager.downloadBtn'), - getCurrentTranslation('$UpdateManager.remindBtn') - ], - defaultId: 1, - title: getCurrentTranslation('$UpdateManager.title'), - message: getCurrentTranslation('$UpdateManager.old-version-msg'), - }; - const response = dialog.showMessageBoxSync(BrowserWindow.getFocusedWindow(), options); - if (response === 1) + const res = match[0].replace(/.*v.(\d+\.\d+\.\d+).*/g, '$1'); + if (app.getVersion() < res) { - //Download latest version - shell.openExternal('https://github.com/TTLApp/time-to-leave/releases/latest'); + const options = { + type: 'question', + buttons: [ + i18NextConfig.getCurrentTranslation('$UpdateManager.dismissBtn'), + i18NextConfig.getCurrentTranslation('$UpdateManager.downloadBtn'), + i18NextConfig.getCurrentTranslation('$UpdateManager.remindBtn') + ], + defaultId: 1, + title: i18NextConfig.getCurrentTranslation('$UpdateManager.title'), + message: i18NextConfig.getCurrentTranslation('$UpdateManager.old-version-msg'), + }; + const response = dialog.showMessageBoxSync(BrowserWindow.getFocusedWindow(), options); + if (response === 1) + { + //Download latest version + shell.openExternal('https://github.com/TTLApp/time-to-leave/releases/latest'); + } + else if (response === 2) + { + const store = new Store(); + // Remind me later + const today = new Date(), + todayDate = getDateStr(today); + store.set('update-remind-me-after', todayDate); + } } - else if (response === 2) + else if (showUpToDateDialog) { - const store = new Store(); - // Remind me later - const today = new Date(), - todayDate = getDateStr(today); - store.set('update-remind-me-after', todayDate); + const options = { + type: 'info', + buttons: [i18NextConfig.getCurrentTranslation('$Menu.ok')], + title: i18NextConfig.getCurrentTranslation('$UpdateManager.title'), + message: i18NextConfig.getCurrentTranslation('$UpdateManager.upto-date-msg') + }; + dialog.showMessageBox(null, options); } } - else if (showUpToDateDialog) - { - const options = { - type: 'info', - buttons: [getCurrentTranslation('$Menu.ok')], - title: getCurrentTranslation('$UpdateManager.title'), - message: getCurrentTranslation('$UpdateManager.upto-date-msg') - }; - dialog.showMessageBox(null, options); - } - } + }); }); - }); - request.end(); + request.end(); + } } -// Enable mocking for some methods, export the mocked versions -const mocks = {'checkForUpdates': _checkForUpdates, 'shouldCheckForUpdates': _shouldCheckForUpdates}; -export const checkForUpdates = async(showUpToDateDialog) => mocks['checkForUpdates'](showUpToDateDialog); -export const shouldCheckForUpdates = () => mocks['shouldCheckForUpdates'](); -export const updateManagerMock = new MockClass(mocks); +export default UpdateManager; diff --git a/js/user-preferences.mjs b/js/user-preferences.mjs index 5c8e696d..d571897f 100644 --- a/js/user-preferences.mjs +++ b/js/user-preferences.mjs @@ -5,7 +5,7 @@ const require = createRequire(import.meta.url); const { ipcRenderer } = require('electron'); -import { validateDate, validateTime } from './time-math.mjs'; +import TimeMath from './time-math.mjs'; import { isValidTheme } from '../renderer/themes.js'; import { getLanguagesCodes } from '../src/configs/app.config.mjs'; @@ -191,8 +191,8 @@ function initPreferencesFileIfNotExistsOrInvalid(filePath = getPreferencesFilePa { const timeValidationEnum = { 'notifications-interval' : () => isNotificationInterval(value), - 'hours-per-day' : () => validateTime(value), - 'break-time-interval' : () => validateTime(value), + 'hours-per-day' : () => TimeMath.validateTime(value), + 'break-time-interval' : () => TimeMath.validateTime(value), }; if (!timeValidationEnum[key]()) { @@ -205,8 +205,8 @@ function initPreferencesFileIfNotExistsOrInvalid(filePath = getPreferencesFilePa 'theme': () => isValidTheme(value), 'view': () => isValidView(value), 'language': () => isValidLocale(value), - 'overall-balance-start-date': () => validateDate(value), - 'update-remind-me-after': () => validateDate(value), + 'overall-balance-start-date': () => TimeMath.validateDate(value), + 'update-remind-me-after': () => TimeMath.validateDate(value), }; if (key in inputEnum) { diff --git a/js/windows.mjs b/js/windows.mjs index 13ef6587..e00580e5 100644 --- a/js/windows.mjs +++ b/js/windows.mjs @@ -5,7 +5,6 @@ import path from 'path'; import { appConfig, rootDir } from './app-config.mjs'; import { getDateStr } from './date-aux.mjs'; -import { MockClass } from '../__mocks__/Mock.mjs'; // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. @@ -14,86 +13,80 @@ global.prefWindow = null; global.tray = null; global.contextMenu = null; -function _openWaiverManagerWindow(mainWindow, event) +class Windows { - if (global.waiverWindow !== null) + static openWaiverManagerWindow(mainWindow, event) { + if (global.waiverWindow !== null) + { + global.waiverWindow.show(); + return; + } + + if (event) + { + const today = new Date(); + global.waiverDay = getDateStr(today); + } + const htmlPath = path.join('file://', rootDir, '/src/workday-waiver.html'); + const dialogCoordinates = Windows.getDialogCoordinates(600, 500, mainWindow); + global.waiverWindow = new BrowserWindow({ width: 600, + height: 500, + x: dialogCoordinates.x, + y: dialogCoordinates.y, + parent: mainWindow, + resizable: true, + icon: appConfig.iconpath, + webPreferences: { + nodeIntegration: true, + preload: path.join(rootDir, '/renderer/preload-scripts/workday-waiver-bridge.mjs'), + contextIsolation: true + } }); + global.waiverWindow.setMenu(null); + global.waiverWindow.loadURL(htmlPath); global.waiverWindow.show(); - return; + global.waiverWindow.on('close', function() + { + global.waiverWindow = null; + mainWindow.webContents.send('WAIVER_SAVED'); + }); + global.waiverWindow.webContents.on('before-input-event', (event, input) => + { + if (input.control && input.shift && input.key.toLowerCase() === 'i') + { + BrowserWindow.getFocusedWindow().webContents.toggleDevTools(); + } + }); } - if (event) + /** + * Return the x and y coordinate for a dialog window, + * so the dialog window is centered on the TTL window. + * Round values, as coordinates have to be integers. + * @param {number} dialogWidth + * @param {number} dialogHeight + * @param {object} mainWindow + */ + static getDialogCoordinates(dialogWidth, dialogHeight, mainWindow) { - const today = new Date(); - global.waiverDay = getDateStr(today); + return { + x : Math.round(mainWindow.getBounds().x + mainWindow.getBounds().width/2 - dialogWidth/2), + y : Math.round(mainWindow.getBounds().y + mainWindow.getBounds().height/2 - dialogHeight/2), + }; } - const htmlPath = path.join('file://', rootDir, '/src/workday-waiver.html'); - const dialogCoordinates = getDialogCoordinates(600, 500, mainWindow); - global.waiverWindow = new BrowserWindow({ width: 600, - height: 500, - x: dialogCoordinates.x, - y: dialogCoordinates.y, - parent: mainWindow, - resizable: true, - icon: appConfig.iconpath, - webPreferences: { - nodeIntegration: true, - preload: path.join(rootDir, '/renderer/preload-scripts/workday-waiver-bridge.mjs'), - contextIsolation: true - } }); - global.waiverWindow.setMenu(null); - global.waiverWindow.loadURL(htmlPath); - global.waiverWindow.show(); - global.waiverWindow.on('close', function() - { - global.waiverWindow = null; - mainWindow.webContents.send('WAIVER_SAVED'); - }); - global.waiverWindow.webContents.on('before-input-event', (event, input) => - { - if (input.control && input.shift && input.key.toLowerCase() === 'i') - { - BrowserWindow.getFocusedWindow().webContents.toggleDevTools(); - } - }); -} -/** - * Return the x and y coordinate for a dialog window, - * so the dialog window is centered on the TTL window. - * Round values, as coordinates have to be integers. - * @param {number} dialogWidth - * @param {number} dialogHeight - * @param {object} mainWindow - */ -function _getDialogCoordinates(dialogWidth, dialogHeight, mainWindow) -{ - return { - x : Math.round(mainWindow.getBounds().x + mainWindow.getBounds().width/2 - dialogWidth/2), - y : Math.round(mainWindow.getBounds().y + mainWindow.getBounds().height/2 - dialogHeight/2), - }; -} - -function resetWindowsElements() -{ - global.waiverWindow = null; - global.prefWindow = null; - global.tray = null; - global.contextMenu = null; -} + static getWaiverWindow() + { + return global.waiverWindow; + } -function getWaiverWindow() -{ - return global.waiverWindow; + static resetWindowsElements() + { + global.waiverWindow = null; + global.prefWindow = null; + global.tray = null; + global.contextMenu = null; + } } -// Enable mocking for some methods, export the mocked versions -const mocks = {'openWaiverManagerWindow': _openWaiverManagerWindow, 'getDialogCoordinates': _getDialogCoordinates}; -export const openWaiverManagerWindow = (mainWindow, event) => mocks['openWaiverManagerWindow'](mainWindow, event); -export const getDialogCoordinates = (dialogWidth, dialogHeight, mainWindow) => mocks['getDialogCoordinates'](dialogWidth, dialogHeight, mainWindow); -export const windowsMock = new MockClass(mocks); - -export { - getWaiverWindow, - resetWindowsElements, -}; +export default Windows; diff --git a/main.mjs b/main.mjs index af179de0..a10c6c11 100644 --- a/main.mjs +++ b/main.mjs @@ -4,12 +4,12 @@ import { app, ipcMain } from 'electron'; import { appConfig } from './js/app-config.mjs'; import { createWindow, createMenu, getMainWindow, triggerStartupDialogs } from './js/main-window.mjs'; -import { createNotification } from './js/notification.mjs'; +import Notification from './js/notification.mjs'; import { handleSquirrelEvent } from './js/squirrel.mjs'; -import { openWaiverManagerWindow } from './js/windows.mjs'; +import Windows from './js/windows.mjs'; import { setupCalendarStore } from './main/calendar-aux.mjs'; import { setupWorkdayWaiverHandlers } from './main/workday-waiver-aux.mjs'; -import { setupI18n, getCurrentTranslation, setLanguageChangedCallback } from './src/configs/i18next.config.mjs'; +import i18NextConfig from './src/configs/i18next.config.mjs'; // Allow require() import { createRequire } from 'module'; @@ -32,7 +32,7 @@ ipcMain.on('SET_WAIVER_DAY', (event, waiverDay) => { global.waiverDay = waiverDay; const mainWindow = getMainWindow(); - openWaiverManagerWindow(mainWindow); + Windows.openWaiverManagerWindow(mainWindow); }); ipcMain.handle('GET_WAIVER_DAY', () => @@ -79,7 +79,7 @@ function checkIdleAndNotify() if (recommendPunchIn) { recommendPunchIn = false; - createNotification(getCurrentTranslation('$Notification.punch-reminder')).show(); + Notification.createNotification(i18NextConfig.getCurrentTranslation('$Notification.punch-reminder')).show(); } } @@ -132,7 +132,7 @@ else app.on('ready', () => { - setupI18n(createMenu).then(() => + i18NextConfig.setupI18n(createMenu).then(() => { // On other platforms the header is automatically set, but on windows // we need to force the name so it doesn't appear as `electron.app.Electron` @@ -143,7 +143,7 @@ app.on('ready', () => createWindow(); createMenu(); setupCalendarStore(); - setLanguageChangedCallback(createMenu); + i18NextConfig.setLanguageChangedCallback(createMenu); triggerStartupDialogs(); setInterval(refreshOnDayChange, 60 * 60 * 1000); const { powerMonitor } = require('electron'); diff --git a/main/calendar-aux.mjs b/main/calendar-aux.mjs index 36d6b1e8..99f0b470 100644 --- a/main/calendar-aux.mjs +++ b/main/calendar-aux.mjs @@ -3,7 +3,7 @@ import { ipcMain } from 'electron'; import Store from 'electron-store'; -import { computeAllTimeBalanceUntilAsync } from '../js/time-balance.mjs'; +import TimeBalance from '../js/time-balance.mjs'; const calendarStore = new Store({name: 'flexible-store'}); @@ -33,7 +33,7 @@ function setupCalendarStore() ipcMain.handle('COMPUTE_ALL_TIME_BALANCE_UNTIL', (event, targetDate) => { - return computeAllTimeBalanceUntilAsync(targetDate); + return TimeBalance.computeAllTimeBalanceUntilAsync(targetDate); }); } diff --git a/package.json b/package.json index f7ce238f..183580fa 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,9 @@ "lint-fix:eslint": "npm run lint:eslint -- --fix", "lint-fix:json": "npm run lint:json -- --write", "lint-fix:markdown": "npm run lint:markdown -- --write", - "package:deb": "electron-packager . time-to-leave --overwrite --asar --platform=linux --arch=x64 --icon=assets/icon-deb.png --prune=true --out=release-builds --ignore=__tests__ --ignore=tests --ignore=./scripts --ignore=Resources --ignore=release-builds --ignore=packages --ignore=coverage_c8 --ignore=.nyc_output --ignore=docs", - "package:mac": "electron-packager . --overwrite --platform=darwin --arch=universal --icon=assets/icon-mac.icns --prune=true --out=release-builds --ignore=__tests__ --ignore=tests --ignore=./scripts --ignore=Resources --ignore=release-builds --ignore=packages --ignore=coverage_c8 --ignore=.nyc_output --ignore=docs", - "package:win": "electron-packager . \"Time to Leave\" --overwrite --platform=win32 --arch=ia32 --icon=assets/icon-win.ico --prune=true --out=release-builds --version-string.CompanyName=CE --version-string.FileDescription=CE --version-string.ProductName=\"Time to Leave\" --ignore=__tests__ --ignore=tests --ignore=./scripts --ignore=Resources --ignore=release-builds --ignore=packages --ignore=coverage_c8 --ignore=.nyc_output --ignore=docs", + "package:deb": "electron-packager . time-to-leave --overwrite --asar --platform=linux --arch=x64 --icon=assets/icon-deb.png --prune=true --out=release-builds --ignore=__tests__ --ignore=tests --ignore=./scripts --ignore=Resources --ignore=release-builds --ignore=packages --ignore=coverage_c8 --ignore=__mocks__ --ignore=docs", + "package:mac": "electron-packager . --overwrite --platform=darwin --arch=universal --icon=assets/icon-mac.icns --prune=true --out=release-builds --ignore=__tests__ --ignore=tests --ignore=./scripts --ignore=Resources --ignore=release-builds --ignore=packages --ignore=coverage_c8 --ignore=__mocks__ --ignore=docs", + "package:win": "electron-packager . \"Time to Leave\" --overwrite --platform=win32 --arch=ia32 --icon=assets/icon-win.ico --prune=true --out=release-builds --version-string.CompanyName=CE --version-string.FileDescription=CE --version-string.ProductName=\"Time to Leave\" --ignore=__tests__ --ignore=tests --ignore=./scripts --ignore=Resources --ignore=release-builds --ignore=packages --ignore=coverage_c8 --ignore=__mocks__ --ignore=docs", "preinstall": "npx npm-force-resolutions", "prepare": "husky", "pretest": "npm run clean", diff --git a/renderer/classes/BaseCalendar.js b/renderer/classes/BaseCalendar.js index b1b2e8f9..8145b4a4 100644 --- a/renderer/classes/BaseCalendar.js +++ b/renderer/classes/BaseCalendar.js @@ -1,15 +1,9 @@ 'use strict'; -import { - hourMinToHourFormatted, - isNegative, - subtractTime, - sumTime, - validateTime -} from '../../js/time-math.mjs'; +import TimeMath from '../../js/time-math.mjs'; import { getDateStr, getMonthLength } from '../../js/date-aux.mjs'; import { generateKey } from '../../js/date-db-formatter.mjs'; -import { getTranslationInLanguageData } from '../i18n-translator.js'; +import i18nTranslator from '../i18n-translator.js'; // Holds the calendar information and manipulation functions class BaseCalendar @@ -59,7 +53,7 @@ class BaseCalendar */ _getTranslation(code) { - return getTranslationInLanguageData(this._languageData.data, code); + return i18nTranslator.getTranslationInLanguageData(this._languageData.data, code); } /** @@ -75,7 +69,7 @@ class BaseCalendar if (balanceElement) { balanceElement.val(balance).removeClass('text-success text-danger') - .html(balance).addClass(isNegative(balance) ? 'text-danger' : 'text-success'); + .html(balance).addClass(TimeMath.isNegative(balance) ? 'text-danger' : 'text-success'); } }) .catch(err => @@ -215,8 +209,8 @@ class BaseCalendar { for (let i = 0; i < validatedTimes.length; i += 2) { - const difference = subtractTime(validatedTimes[i], validatedTimes[i + 1]); - dayTotal = sumTime(dayTotal, difference); + const difference = TimeMath.subtractTime(validatedTimes[i], validatedTimes[i + 1]); + dayTotal = TimeMath.sumTime(dayTotal, difference); if (validatedTimes[i] >= validatedTimes[i + 1]) { timesAreProgressing = false; @@ -493,9 +487,9 @@ class BaseCalendar _calculateBreakEnd(breakBegin) { const breakInterval = this._getBreakTimeInterval(); - let breakEnd = sumTime(breakBegin, breakInterval); + let breakEnd = TimeMath.sumTime(breakBegin, breakInterval); - breakEnd = validateTime(breakEnd) ? breakEnd : '23:59'; + breakEnd = TimeMath.validateTime(breakEnd) ? breakEnd : '23:59'; return breakEnd; } @@ -523,7 +517,7 @@ class BaseCalendar this._addTodayEntries(); } - const value = hourMinToHourFormatted(hour, min); + const value = TimeMath.hourMinToHourFormatted(hour, min); const key = generateKey(year, month, day); const inputs = $('#' + key + ' input[type="time"]'); @@ -618,8 +612,8 @@ class BaseCalendar { for (let i = 0; i < validatedTimes.length; i += 2) { - const difference = subtractTime(validatedTimes[i], validatedTimes[i + 1]); - dayTotal = sumTime(dayTotal, difference); + const difference = TimeMath.subtractTime(validatedTimes[i], validatedTimes[i + 1]); + dayTotal = TimeMath.sumTime(dayTotal, difference); if (validatedTimes[i] >= validatedTimes[i + 1]) { timesAreProgressing = false; @@ -650,8 +644,8 @@ class BaseCalendar let timesAreProgressing = true; for (let i = 0; i < smallestMultipleOfTwo; i += 2) { - const difference = subtractTime(validatedTimes[i], validatedTimes[i + 1]); - dayTotal = sumTime(dayTotal, difference); + const difference = TimeMath.subtractTime(validatedTimes[i], validatedTimes[i + 1]); + dayTotal = TimeMath.sumTime(dayTotal, difference); if (validatedTimes[i] >= validatedTimes[i + 1]) { timesAreProgressing = false; @@ -660,8 +654,8 @@ class BaseCalendar if (timesAreProgressing) { const lastTime = validatedTimes[validatedTimes.length-1]; - const remainingTime = subtractTime(dayTotal, this._getHoursPerDay()); - leaveBy = sumTime(lastTime, remainingTime); + const remainingTime = TimeMath.subtractTime(dayTotal, this._getHoursPerDay()); + leaveBy = TimeMath.sumTime(lastTime, remainingTime); } } return leaveBy; @@ -681,7 +675,7 @@ class BaseCalendar { for (const time of values) { - validatedTimes.push(validateTime(time) ? time : '--:--'); + validatedTimes.push(TimeMath.validateTime(time) ? time : '--:--'); } } diff --git a/renderer/classes/DayCalendar.js b/renderer/classes/DayCalendar.js index f20117fa..f3d082b9 100644 --- a/renderer/classes/DayCalendar.js +++ b/renderer/classes/DayCalendar.js @@ -1,12 +1,6 @@ 'use strict'; -import { - isNegative, - multiplyTime, - subtractTime, - sumTime, - validateTime -} from '../../js/time-math.mjs'; +import TimeMath from '../../js/time-math.mjs'; import { getDateStr, getMonthLength } from '../../js/date-aux.mjs'; import { generateKey } from '../../js/date-db-formatter.mjs'; import { BaseCalendar } from './BaseCalendar.js'; @@ -410,21 +404,21 @@ class DayCalendar extends BaseCalendar if (dayTotal !== undefined) { countDays = true; - monthTotalWorked = sumTime(monthTotalWorked, dayTotal); + monthTotalWorked = TimeMath.sumTime(monthTotalWorked, dayTotal); } if (countDays) { workingDaysToCompute += 1; } } - const monthTotalToWork = multiplyTime(this._getHoursPerDay(), workingDaysToCompute * -1); - const balance = sumTime(monthTotalToWork, monthTotalWorked); + const monthTotalToWork = TimeMath.multiplyTime(this._getHoursPerDay(), workingDaysToCompute * -1); + const balance = TimeMath.sumTime(monthTotalToWork, monthTotalWorked); const balanceElement = $('#month-balance'); if (balanceElement) { balanceElement.html(balance); balanceElement.removeClass('text-success text-danger'); - balanceElement.addClass(isNegative(balance) ? 'text-danger' : 'text-success'); + balanceElement.addClass(TimeMath.isNegative(balance) ? 'text-danger' : 'text-success'); } } @@ -447,10 +441,10 @@ class DayCalendar extends BaseCalendar const dayTotal = $('.day-total span').html(); if (dayTotal !== undefined && dayTotal.length > 0) { - const dayBalance = subtractTime(this._getHoursPerDay(), dayTotal); + const dayBalance = TimeMath.subtractTime(this._getHoursPerDay(), dayTotal); $('#leave-day-balance').val(dayBalance); $('#leave-day-balance').removeClass('text-success text-danger'); - $('#leave-day-balance').addClass(isNegative(dayBalance) ? 'text-danger' : 'text-success'); + $('#leave-day-balance').addClass(TimeMath.isNegative(dayBalance) ? 'text-danger' : 'text-success'); $('#summary-unfinished-day').addClass('hidden'); $('#summary-finished-day').removeClass('hidden'); } @@ -555,11 +549,11 @@ class DayCalendar extends BaseCalendar { timeEnd = element.value; - if (validateTime(timeEnd) && validateTime(timeStart)) + if (TimeMath.validateTime(timeEnd) && TimeMath.validateTime(timeStart)) { if (timeEnd > timeStart) { - $(element).closest('.row-entry-pair').prev().find('div.interval').html(subtractTime(timeStart, timeEnd)); + $(element).closest('.row-entry-pair').prev().find('div.interval').html(TimeMath.subtractTime(timeStart, timeEnd)); } timeStart = ''; timeEnd = ''; diff --git a/renderer/classes/MonthCalendar.js b/renderer/classes/MonthCalendar.js index 4926b437..2ae3ecb7 100644 --- a/renderer/classes/MonthCalendar.js +++ b/renderer/classes/MonthCalendar.js @@ -1,12 +1,6 @@ 'use strict'; -import { - isNegative, - multiplyTime, - subtractTime, - sumTime, - validateTime -} from '../../js/time-math.mjs'; +import TimeMath from '../../js/time-math.mjs'; import { getMonthLength } from '../../js/date-aux.mjs'; import { generateKey } from '../../js/date-db-formatter.mjs'; import { @@ -531,21 +525,21 @@ class MonthCalendar extends BaseCalendar if (dayTotal !== undefined && dayTotal.length !== 0) { countDays = true; - monthTotalWorked = sumTime(monthTotalWorked, dayTotal); + monthTotalWorked = TimeMath.sumTime(monthTotalWorked, dayTotal); } if (countDays) { workingDaysToCompute += 1; } } - const monthTotalToWork = multiplyTime(this._getHoursPerDay(), workingDaysToCompute * -1); - const balance = sumTime(monthTotalToWork, monthTotalWorked); + const monthTotalToWork = TimeMath.multiplyTime(this._getHoursPerDay(), workingDaysToCompute * -1); + const balance = TimeMath.sumTime(monthTotalToWork, monthTotalWorked); const balanceElement = $('#month-balance'); if (balanceElement) { balanceElement.val(balance); balanceElement.removeClass('text-success text-danger'); - balanceElement.addClass(isNegative(balance) ? 'text-danger' : 'text-success'); + balanceElement.addClass(TimeMath.isNegative(balance) ? 'text-danger' : 'text-success'); } this._updateAllTimeBalance(); } @@ -596,7 +590,7 @@ class MonthCalendar extends BaseCalendar if (dayTotal) { - monthTotal = sumTime(monthTotal, dayTotal); + monthTotal = TimeMath.sumTime(monthTotal, dayTotal); } } const monthDayInput = $('#month-day-input'); @@ -634,10 +628,10 @@ class MonthCalendar extends BaseCalendar const dayTotal = $('#' + dateKey).parent().find(' .day-total span').html(); if (dayTotal !== undefined && dayTotal.length > 0) { - const dayBalance = subtractTime(this._getHoursPerDay(), dayTotal); + const dayBalance = TimeMath.subtractTime(this._getHoursPerDay(), dayTotal); $('#leave-day-balance').html(dayBalance); $('#leave-day-balance').removeClass('text-success text-danger'); - $('#leave-day-balance').addClass(isNegative(dayBalance) ? 'text-danger' : 'text-success'); + $('#leave-day-balance').addClass(TimeMath.isNegative(dayBalance) ? 'text-danger' : 'text-success'); $('#summary-unfinished-day').addClass('hidden'); $('#summary-finished-day').removeClass('hidden'); } @@ -660,11 +654,11 @@ class MonthCalendar extends BaseCalendar { timeEnd = element.value; - if (validateTime(timeEnd) && validateTime(timeStart)) + if (TimeMath.validateTime(timeEnd) && TimeMath.validateTime(timeStart)) { if (timeEnd > timeStart) { - $(element).closest('.row-time').prev().find('span').html(subtractTime(timeStart, timeEnd)); + $(element).closest('.row-time').prev().find('span').html(TimeMath.subtractTime(timeStart, timeEnd)); } timeStart = ''; timeEnd = ''; diff --git a/renderer/i18n-translator.js b/renderer/i18n-translator.js index 78829b30..d423e4ef 100644 --- a/renderer/i18n-translator.js +++ b/renderer/i18n-translator.js @@ -1,7 +1,5 @@ 'use strict'; -import { MockClass } from '../__mocks__/Mock.mjs'; - function getDataRecursive(array, keyList) { if (keyList.length === 0) @@ -18,43 +16,42 @@ function getDataRecursive(array, keyList) } } -function _getTranslationInLanguageData(languageData, key) +class i18nTranslator { - const keyList = key.split('.'); - return getDataRecursive(languageData['translation'], keyList); -} - -function _translatePage(language, languageData, windowName) -{ - $('html').attr('lang', language); + static getTranslationInLanguageData(languageData, key) + { + const keyList = key.split('.'); + return getDataRecursive(languageData['translation'], keyList); + } - function translateElement(element) + static translatePage(language, languageData, windowName) { - const attr = $(element).attr('data-i18n'); - if (typeof attr !== 'undefined' && attr !== false && attr.length > 0) + $('html').attr('lang', language); + + function translateElement(element) { - $(element).html(getTranslationInLanguageData(languageData, attr)); + const attr = $(element).attr('data-i18n'); + if (typeof attr !== 'undefined' && attr !== false && attr.length > 0) + { + $(element).html(i18nTranslator.getTranslationInLanguageData(languageData, attr)); + } } - } - const callback = (key, value) => { translateElement(value); }; - $('title').each(callback); - $('body').each(callback); - $('p').each(callback); - $('label').each(callback); - $('div').each(callback); - $('span').each(callback); - $('option').each(callback); - $('th').each(callback); - $('a').each(callback); - $('button').each(callback); - - const titleAttr = `$${windowName}.title`; - $(document).attr('title', `Time to Leave - ${getTranslationInLanguageData(languageData, titleAttr)}`); + const callback = (key, value) => { translateElement(value); }; + $('title').each(callback); + $('body').each(callback); + $('p').each(callback); + $('label').each(callback); + $('div').each(callback); + $('span').each(callback); + $('option').each(callback); + $('th').each(callback); + $('a').each(callback); + $('button').each(callback); + + const titleAttr = `$${windowName}.title`; + $(document).attr('title', `Time to Leave - ${i18nTranslator.getTranslationInLanguageData(languageData, titleAttr)}`); + } } -// Enable mocking for some methods, export the mocked versions -const mocks = {'getTranslationInLanguageData': _getTranslationInLanguageData, 'translatePage': _translatePage}; -export const getTranslationInLanguageData = (languageData, key) => mocks['getTranslationInLanguageData'](languageData, key); -export const translatePage = (language, languageData, windowName) => mocks['translatePage'](language, languageData, windowName); -export const i18nTranslatorMock = new MockClass(mocks); +export default i18nTranslator; diff --git a/src/configs/i18next.config.mjs b/src/configs/i18next.config.mjs index 618d8b17..2de69a4a 100644 --- a/src/configs/i18next.config.mjs +++ b/src/configs/i18next.config.mjs @@ -1,18 +1,12 @@ 'use strict'; import { ipcMain } from 'electron'; +import i18n from 'i18next'; import i18nextBackend from 'i18next-fs-backend'; import path from 'path'; import { fallbackLng, getLanguagesCodes } from './app.config.mjs'; import { appConfig } from '../../js/app-config.mjs'; -import { MockClass } from '../../__mocks__/Mock.mjs'; - -// Allow require() -import { createRequire } from 'module'; -const require = createRequire(import.meta.url); - -const i18n = require('i18next'); // TODO: make async below again import { getUserLanguage } from '../../js/user-preferences.mjs'; @@ -38,46 +32,11 @@ const i18nextOptions = { } }; -function setupI18n() -{ - const userLanguage = getUserLanguage(); - - return new Promise((resolve) => - { - i18n.use(i18nextBackend); - - // initialize if not already initialized - if (!i18n.isInitialized) - { - i18n.init(i18nextOptions, () => - { - i18n.changeLanguage(userLanguage).then(() => - { - resolve(); - }); - }); - } - }); -} - -function setLanguageChangedCallback(languageChangedCallback) -{ - i18n.on('languageChanged', () => - { - languageChangedCallback(); - }); -} - -function changeLanguage(language) -{ - return i18n.changeLanguage(language); -} - ipcMain.handle('CHANGE_LANGUAGE', (event, language) => { return new Promise((resolve) => { - changeLanguage(language).then(() => + i18NextConfig.changeLanguage(language).then(() => { resolve(getCurrentLanguageData()); }); @@ -97,18 +56,47 @@ ipcMain.handle('GET_LANGUAGE_DATA', () => }; }); -function _getCurrentTranslation(code) +class i18NextConfig { - return i18n.t(code); -} + static getCurrentTranslation(code) + { + return i18n.t(code); + } -// Enable mocking for some methods, export the mocked versions -const mocks = {'getCurrentTranslation': _getCurrentTranslation}; -export const getCurrentTranslation = (code) => mocks['getCurrentTranslation'](code); -export const i18nMock = new MockClass(mocks); + static changeLanguage(language) + { + return i18n.changeLanguage(language); + } -export { - changeLanguage, - setLanguageChangedCallback, - setupI18n, -}; + static setLanguageChangedCallback(languageChangedCallback) + { + i18n.on('languageChanged', () => + { + languageChangedCallback(); + }); + } + + static setupI18n() + { + const userLanguage = getUserLanguage(); + + return new Promise((resolve) => + { + i18n.use(i18nextBackend); + + // initialize if not already initialized + if (!i18n.isInitialized) + { + i18n.init(i18nextOptions, () => + { + i18n.changeLanguage(userLanguage).then(() => + { + resolve(); + }); + }); + } + }); + } +} + +export default i18NextConfig; diff --git a/src/preferences.js b/src/preferences.js index 92f5d6da..74585cb7 100644 --- a/src/preferences.js +++ b/src/preferences.js @@ -1,7 +1,7 @@ 'use strict'; import { applyTheme } from '../renderer/themes.js'; -import { getTranslationInLanguageData, translatePage } from '../renderer/i18n-translator.js'; +import i18nTranslator from '../renderer/i18n-translator.js'; // Global values for preferences page let usersStyles; @@ -34,7 +34,7 @@ function listenerLanguage() preferences['language'] = this.value; window.mainApi.changeLanguagePromise(this.value).then((languageData) => { - translatePage(this.value, languageData, 'Preferences'); + i18nTranslator.translatePage(this.value, languageData, 'Preferences'); window.mainApi.notifyNewPreferences(preferences); }); }); @@ -46,7 +46,7 @@ function setupLanguages() listenerLanguage(); window.mainApi.getLanguageDataPromise().then(languageData => { - translatePage(usersStyles['language'], languageData.data, 'Preferences'); + i18nTranslator.translatePage(usersStyles['language'], languageData.data, 'Preferences'); }); } @@ -224,11 +224,11 @@ function setupListeners() { const options = { type: 'question', - buttons: [getTranslationInLanguageData(languageData.data, '$Preferences.yes-please'), getTranslationInLanguageData(languageData.data, '$Preferences.no-thanks')], + buttons: [i18nTranslator.getTranslationInLanguageData(languageData.data, '$Preferences.yes-please'), i18nTranslator.getTranslationInLanguageData(languageData.data, '$Preferences.no-thanks')], defaultId: 1, cancelId: 1, - title: getTranslationInLanguageData(languageData.data, '$Preferences.reset-preferences'), - message: getTranslationInLanguageData(languageData.data, '$Preferences.confirm-reset-preferences'), + title: i18nTranslator.getTranslationInLanguageData(languageData.data, '$Preferences.reset-preferences'), + message: i18nTranslator.getTranslationInLanguageData(languageData.data, '$Preferences.confirm-reset-preferences'), }; window.mainApi.showDialogSync(options).then((result) => { @@ -237,8 +237,8 @@ function setupListeners() resetContent(); const optionsReset = { type: 'info', - message: getTranslationInLanguageData(languageData.data, '$Preferences.reset-preferences'), - detail: getTranslationInLanguageData(languageData.data, '$Preferences.reset-success'), + message: i18nTranslator.getTranslationInLanguageData(languageData.data, '$Preferences.reset-preferences'), + detail: i18nTranslator.getTranslationInLanguageData(languageData.data, '$Preferences.reset-success'), }; window.mainApi.showDialogSync(optionsReset); } diff --git a/src/workday-waiver.js b/src/workday-waiver.js index e625b400..765d2972 100644 --- a/src/workday-waiver.js +++ b/src/workday-waiver.js @@ -1,8 +1,8 @@ 'use strict'; import { applyTheme } from '../renderer/themes.js'; -import { getTranslationInLanguageData, translatePage } from '../renderer/i18n-translator.js'; -import { validateTime, diffDays } from '../js/time-math.mjs'; +import i18nTranslator from '../renderer/i18n-translator.js'; +import TimeMath from '../js/time-math.mjs'; import { getDateStr } from '../js/date-aux.mjs'; let languageData; @@ -10,7 +10,7 @@ let userPreferences; function getTranslation(code) { - return getTranslationInLanguageData(languageData.data, code); + return i18nTranslator.getTranslationInLanguageData(languageData.data, code); } function refreshDataForTest(data) @@ -107,13 +107,13 @@ async function addWaiver() reason = $('#reason').val(), hours = $('#hours').val(); - if (!(validateTime(hours))) + if (!(TimeMath.validateTime(hours))) { // The error is shown in the page, no need to handle it here return false; } - const diff = diffDays(startDate, endDate); + const diff = TimeMath.diffDays(startDate, endDate); if (diff < 0) { @@ -284,7 +284,7 @@ async function iterateOnHolidays(func) const startDate = new Date(holiday['start']), endDate = new Date(holiday['end']), reason = holiday['name']; - const diff = diffDays(startDate, endDate) - 1; + const diff = TimeMath.diffDays(startDate, endDate) - 1; const tempDate = new Date(startDate); for (let i = 0; i <= diff; i++) { @@ -477,7 +477,7 @@ $(async() => } }); - translatePage(languageData.language, languageData.data, 'WorkdayWaiver'); + i18nTranslator.translatePage(languageData.language, languageData.data, 'WorkdayWaiver'); }); export {