diff --git a/__mocks__/Mock.mjs b/__mocks__/Mock.mjs index 8aad906f..ba364bf5 100644 --- a/__mocks__/Mock.mjs +++ b/__mocks__/Mock.mjs @@ -57,7 +57,7 @@ class MockClass } /** - * Test related function to get mocking exported methods + * Restore a single mocked method * @param {string} methodName */ restoreMock(methodName) @@ -76,6 +76,20 @@ class MockClass this._mocked[methodName] = false; } } + + /** + * Restore all mocked methods + */ + restoreAll() + { + for (const [methodName, isMocked] of Object.entries(this._mocked)) + { + if (isMocked) + { + this.restoreMock(methodName); + } + } + } } export { diff --git a/__tests__/__main__/menus.mjs b/__tests__/__main__/menus.mjs index c6a0b5ae..ba2f8daa 100644 --- a/__tests__/__main__/menus.mjs +++ b/__tests__/__main__/menus.mjs @@ -1,6 +1,9 @@ +/* eslint-disable no-undef */ 'use strict'; -import { createStubInstance, stub } from 'sinon'; +import assert from 'assert'; +import { app, BrowserWindow, clipboard, dialog, shell } from 'electron'; +import { stub } from 'sinon'; import { getContextMenuTemplate, @@ -15,61 +18,22 @@ import { i18nMock } from '../../src/configs/i18next.config.mjs'; i18nMock.mock('getCurrentTranslation', stub().callsFake((code) => code)); import { windowsMock } from '../../js/windows.mjs'; -windowsMock.mock('openWaiverManagerWindow', stub()); windowsMock.mock('getDialogCoordinates', stub()); -import electron from 'electron'; -const { app, BrowserWindow, shell, dialog, clipboard } = electron; - -const electronStub = createStubInstance(electron, - { - ipcRenderer: { - invoke: stub().onFirstCall().returns('./').onSecondCall().returns('./dummy_file.txt'), - }, - app: { - quit: stub() - }, - BrowserWindow: { - getFocusedWindow: () => - { - return { - reload: stub() - }; - } - }, - shell: { - openExternal: stub() - }, - dialog: { - showSaveDialogSync: stub(), - showMessageBox: stub(), - showMessageBoxSync: stub(), - showOpenDialogSync: stub() - }, - clipboard: { - writeText: stub() - } - } -); +// stub(ipcRenderer, 'invoke').onFirstCall().returns('./').onSecondCall().returns('./dummy_file.txt'); +// stub(app, 'quit'); import { notificationMock } from '../../js/notification.mjs'; -notificationMock.mock('createNotification', stub().callsFake(() => ({ - show: stub() - })) -); import { updateManagerMock } from '../../js/update-manager.mjs'; updateManagerMock.mock('checkForUpdates', stub()); import { importExportMock } from '../../js/import-export.mjs'; -importExportMock.mock('exportDatabaseToFile', stub()); importExportMock.mock('importDatabaseFromFile', stub()); import Store from 'electron-store'; -const storeStub = stub(Store, 'constructor'); -const storeClearStub = stub(Store, 'clear'); +stub(Store, 'constructor'); -const ElectronStore = require('electron-store'); describe('menus.js', () => { const mocks = {}; @@ -97,7 +61,6 @@ describe('menus.js', () => { for (const t of tests) { - assert.strictEqual(menu[t.field], true); assert.strictEqual(typeof menu[t.field], t.type); } if ('id' in menu) @@ -114,17 +77,18 @@ describe('menus.js', () => it('Should open waiver window', (done) => { - mocks.waiver = jest.spyOn(windows, 'openWaiverManagerWindow').mockImplementationOnce( () => + windowsMock.mock('openWaiverManagerWindow', stub().callsFake(() => { done(); - }); + })); getMainMenuTemplate()[0].click(); }); it('Should close app', (done) => { - mocks.quit = jest.spyOn(app, 'quit').mockImplementationOnce(() => + const quitStub = stub(app, 'quit').callsFake(() => { + quitStub.restore(); done(); }); getMainMenuTemplate()[2].click(); @@ -148,7 +112,6 @@ describe('menus.js', () => ]; for (const t of tests) { - assert.strictEqual(menu[t.field], true); assert.strictEqual(typeof menu[t.field], t.type); } }); @@ -165,9 +128,11 @@ describe('menus.js', () => } } }; - mocks.createNotificationSpy = jest.spyOn(notification, 'createNotification'); + notificationMock.mock('createNotification', stub().callsFake(() => ({ + show: stub() + }))); getContextMenuTemplate(mainWindow)[0].click(); - expect(mocks.createNotificationSpy).toBeCalledTimes(1); + assert.strictEqual(notificationMock.getMock('createNotification').calledOnce, true); }); it('Should create notification on click', (done) => @@ -180,8 +145,9 @@ describe('menus.js', () => it('Should show window on click', (done) => { - mocks.quit = jest.spyOn(app, 'quit').mockImplementationOnce(() => + const quitStub = stub(app, 'quit').callsFake(() => { + quitStub.restore(); done(); }); getContextMenuTemplate({})[2].click(); @@ -206,7 +172,6 @@ describe('menus.js', () => ]; for (const t of tests) { - assert.strictEqual(menu[t.field], true); assert.strictEqual(typeof menu[t.field], t.type); } }); @@ -222,11 +187,11 @@ describe('menus.js', () => } } }; - mocks.createNotificationSpy = jest.spyOn(notification, 'createNotification').mockImplementation(() => ({ + notificationMock.mock('createNotification', stub().callsFake(() => ({ show: done - })); + }))); getDockMenuTemplate(mainWindow)[0].click(); - expect(mocks.createNotificationSpy).toBeCalledTimes(1); + assert.strictEqual(notificationMock.getMock('createNotification').calledOnce, true); }); }); @@ -247,7 +212,6 @@ describe('menus.js', () => ]; for (const t of tests) { - assert.strictEqual(menu[t.field], true); assert.strictEqual(typeof menu[t.field], t.type); } }); @@ -255,28 +219,36 @@ describe('menus.js', () => it('Should reload window', (done) => { - mocks.window = jest.spyOn(BrowserWindow, 'getFocusedWindow').mockImplementation(() => + const windowStub = stub(BrowserWindow, 'getFocusedWindow').callsFake(() => { return { - reload: () => done() + reload: () => + { + windowStub.restore(); + done(); + } }; }); getViewMenuTemplate()[0].click(); - expect(mocks.window).toBeCalledTimes(1); + assert.strictEqual(windowStub.calledOnce, true); }); it('Should toggle devtools', (done) => { - mocks.window = jest.spyOn(BrowserWindow, 'getFocusedWindow').mockImplementation(() => + const windowStub = stub(BrowserWindow, 'getFocusedWindow').callsFake(() => { return { - toggleDevTools: () => done() + toggleDevTools: () => + { + windowStub.restore(); + done(); + } }; }); getViewMenuTemplate()[1].click(); - expect(mocks.window).toBeCalledTimes(1); + assert.strictEqual(windowStub.calledOnce, true); }); }); @@ -303,7 +275,6 @@ describe('menus.js', () => { for (const t of tests) { - assert.strictEqual(menu[t.field], true); assert.strictEqual(typeof menu[t.field], t.type); } } @@ -312,9 +283,10 @@ describe('menus.js', () => it('Should open github', (done) => { - mocks.window = jest.spyOn(shell, 'openExternal').mockImplementation((key) => + const shellStub = stub(shell, 'openExternal').callsFake((key) => { assert.strictEqual(key, 'https://github.com/thamara/time-to-leave'); + shellStub.restore(); done(); }); getHelpMenuTemplate()[0].click(); @@ -322,56 +294,65 @@ describe('menus.js', () => it('Should open github', (done) => { - mocks.window = jest.spyOn(updateManager, 'checkForUpdates').mockImplementation((key) => + updateManagerMock.mock('checkForUpdates', stub().callsFake((key) => { assert.strictEqual(key, true); done(); - }); + })); getHelpMenuTemplate()[1].click(); }); it('Should open feedback', (done) => { - mocks.window = jest.spyOn(shell, 'openExternal').mockImplementation((key) => + const shellStub = stub(shell, 'openExternal').callsFake((key) => { assert.strictEqual(key, 'https://github.com/thamara/time-to-leave/issues/new'); + shellStub.restore(); done(); }); getHelpMenuTemplate()[2].click(); }); - it('Should show about message box and writ to clipboard', () => + it('Should show about message box and write to clipboard', (done) => { - mocks.writeText = jest.spyOn(clipboard, 'writeText').mockImplementation(() => {}); - mocks.showMessageBox = jest.spyOn(dialog, 'showMessageBox').mockResolvedValue({response: 0}); + const writeTextStub = stub(clipboard, 'writeText'); + const showMessageBoxStub = stub(dialog, 'showMessageBox').resolves({response: 0}); getHelpMenuTemplate({})[4].click(); setTimeout(() => { - expect(mocks.showMessageBox).toHaveBeenCalledTimes(1); - expect(mocks.writeText).toHaveBeenCalledTimes(1); + assert.strictEqual(showMessageBoxStub.calledOnce, true); + assert.strictEqual(writeTextStub.calledOnce, true); + showMessageBoxStub.restore(); + writeTextStub.restore(); + done(); }, 1000); }); it('Should show about message box', () => { - mocks.writeText = jest.spyOn(clipboard, 'writeText').mockImplementation(() => {}); - mocks.showMessageBox = jest.spyOn(dialog, 'showMessageBox').mockResolvedValue({response: 1}); + const writeTextStub = stub(clipboard, 'writeText'); + const showMessageBoxStub = stub(dialog, 'showMessageBox').resolves({response: 1}); getHelpMenuTemplate({})[4].click(); - expect(mocks.showMessageBox).toHaveBeenCalledTimes(1); - expect(mocks.writeText).toHaveBeenCalledTimes(0); + assert.strictEqual(showMessageBoxStub.calledOnce, true); + assert.strictEqual(writeTextStub.notCalled, true); + showMessageBoxStub.restore(); + writeTextStub.restore(); }); it('Should show about message box', (done) => { - mocks.consoleLog = jest.spyOn(console, 'log').mockImplementation(); - mocks.writeText = jest.spyOn(clipboard, 'writeText').mockImplementation(() => {}); - mocks.showMessageBox = jest.spyOn(dialog, 'showMessageBox').mockRejectedValue({response: 1}); + const consoleSpy = stub(console, 'log'); + const writeTextStub = stub(clipboard, 'writeText'); + const showMessageBoxStub = stub(dialog, 'showMessageBox').rejects({response: 1}); getHelpMenuTemplate({})[4].click(); - expect(mocks.showMessageBox).toHaveBeenCalledTimes(1); - expect(mocks.writeText).toHaveBeenCalledTimes(0); + assert.strictEqual(showMessageBoxStub.calledOnce, true); + assert.strictEqual(writeTextStub.notCalled, true); // When the rejection happens, we call console.log with the response setTimeout(() => { - expect(mocks.consoleLog).toHaveBeenCalledWith({response: 1}); + assert.strictEqual(consoleSpy.calledWith({response: 1}), true); + consoleSpy.restore(); + showMessageBoxStub.restore(); + writeTextStub.restore(); done(); }, 500); }); @@ -379,6 +360,17 @@ describe('menus.js', () => describe('getEditMenuTemplate', () => { + let showMessageBoxStub; + before(() => + { + showMessageBoxStub = stub(dialog, 'showMessageBox'); + }); + + beforeEach(() => + { + showMessageBoxStub.resetHistory(); + }); + it('Should have 10 options', () => { assert.strictEqual(getEditMenuTemplate().length, 10); @@ -399,7 +391,6 @@ describe('menus.js', () => { for (const t of tests) { - assert.strictEqual(menu[t.field], true); assert.strictEqual(typeof menu[t.field], t.type); } if ('id' in menu) @@ -424,29 +415,28 @@ describe('menus.js', () => it('Should show dialog for exporting db', () => { - mocks.showSaveDialogSync = jest.spyOn(dialog, 'showSaveDialogSync').mockImplementation(() => true); - mocks.showMessageBox = jest.spyOn(dialog, 'showMessageBox').mockImplementation(() =>{ }); - mocks.export = jest.spyOn(importExport, 'exportDatabaseToFile').mockImplementation(() =>{ }); + const showDialogSyncStub = stub(dialog, 'showSaveDialogSync').returns(true); + importExportMock.mock('exportDatabaseToFile', stub()); getEditMenuTemplate()[7].click(); - expect(mocks.showSaveDialogSync).toHaveBeenCalledTimes(1); - expect(mocks.showMessageBox).toHaveBeenCalledTimes(1); - expect(mocks.export).toHaveBeenCalledTimes(1); + assert.strictEqual(showDialogSyncStub.calledOnce, true); + assert.strictEqual(showMessageBoxStub.calledOnce, true); + assert.strictEqual(importExportMock.getMock('exportDatabaseToFile').calledOnce, true); + showDialogSyncStub.restore(); }); it('Should not show dialog for exporting db', () => { - mocks.showSaveDialogSync = jest.spyOn(dialog, 'showSaveDialogSync').mockImplementation(() => false); - mocks.showMessageBox = jest.spyOn(dialog, 'showMessageBox').mockImplementation(() =>{ }); - mocks.export = jest.spyOn(importExport, 'exportDatabaseToFile').mockImplementation(() =>{ }); + const showDialogSyncStub = stub(dialog, 'showSaveDialogSync').returns(false); + importExportMock.mock('exportDatabaseToFile', stub()); getEditMenuTemplate()[7].click(); - expect(mocks.showSaveDialogSync).toHaveBeenCalledTimes(1); - expect(mocks.showMessageBox).toHaveBeenCalledTimes(0); - expect(mocks.export).toHaveBeenCalledTimes(0); + assert.strictEqual(showDialogSyncStub.calledOnce, true); + assert.strictEqual(showMessageBoxStub.notCalled, true); + assert.strictEqual(importExportMock.getMock('exportDatabaseToFile').notCalled, true); + showDialogSyncStub.restore(); }); it('Should show dialog for importing db', () => { - expect.assertions(5); const mainWindow = { webContents: { send: (key) => @@ -455,23 +445,23 @@ describe('menus.js', () => } } }; - mocks.showOpenDialogSync = jest.spyOn(dialog, 'showOpenDialogSync').mockImplementation(() => true); - mocks.showMessageBox = jest.spyOn(dialog, 'showMessageBox').mockImplementation(() =>{ }); - mocks.showMessageBoxSync = jest.spyOn(dialog, 'showMessageBoxSync').mockImplementation(() => 0); - mocks.export = jest.spyOn(importExport, 'importDatabaseFromFile').mockImplementation(() => ({ + const showOpenDialogSyncStub = stub(dialog, 'showOpenDialogSync').returns(true); + const showMessageBoxSyncStub = stub(dialog, 'showMessageBoxSync').returns(0); + importExportMock.mock('importDatabaseFromFile', stub().returns({ result: true, failed: 0 })); getEditMenuTemplate(mainWindow)[8].click(); - expect(mocks.showOpenDialogSync).toHaveBeenCalledTimes(1); - expect(mocks.showMessageBoxSync).toHaveBeenCalledTimes(1); - expect(mocks.showMessageBox).toHaveBeenCalledTimes(1); - expect(mocks.export).toHaveBeenCalledTimes(1); + assert.strictEqual(showOpenDialogSyncStub.calledOnce, true); + assert.strictEqual(showMessageBoxSyncStub.calledOnce, true); + assert.strictEqual(showMessageBoxStub.calledOnce, true); + assert.strictEqual(importExportMock.getMock('importDatabaseFromFile').calledOnce, true); + showOpenDialogSyncStub.restore(); + showMessageBoxSyncStub.restore(); }); it('Should show fail dialog for importing db', () => { - expect.assertions(5); const mainWindow = { webContents: { send: (key) => @@ -480,23 +470,23 @@ describe('menus.js', () => } } }; - mocks.showOpenDialogSync = jest.spyOn(dialog, 'showOpenDialogSync').mockImplementation(() => true); - mocks.showMessageBox = jest.spyOn(dialog, 'showMessageBox').mockImplementation(() =>{ }); - mocks.showMessageBoxSync = jest.spyOn(dialog, 'showMessageBoxSync').mockImplementation(() => 0); - mocks.export = jest.spyOn(importExport, 'importDatabaseFromFile').mockImplementation(() => ({ + const showOpenDialogSyncStub = stub(dialog, 'showOpenDialogSync').returns(true); + const showMessageBoxSyncStub = stub(dialog, 'showMessageBoxSync').returns(0); + importExportMock.mock('importDatabaseFromFile', stub().returns({ result: false, failed: 1 })); getEditMenuTemplate(mainWindow)[8].click(); - expect(mocks.showOpenDialogSync).toHaveBeenCalledTimes(1); - expect(mocks.showMessageBoxSync).toHaveBeenCalledTimes(2); - expect(mocks.showMessageBox).toHaveBeenCalledTimes(0); - expect(mocks.export).toHaveBeenCalledTimes(1); + assert.strictEqual(showOpenDialogSyncStub.calledOnce, true); + assert.strictEqual(showMessageBoxSyncStub.calledTwice, true); + assert.strictEqual(showMessageBoxStub.notCalled, true); + assert.strictEqual(importExportMock.getMock('importDatabaseFromFile').calledOnce, true); + showOpenDialogSyncStub.restore(); + showMessageBoxSyncStub.restore(); }); it('Should show fail dialog for importing db', () => { - expect.assertions(5); const mainWindow = { webContents: { send: (key) => @@ -505,53 +495,56 @@ describe('menus.js', () => } } }; - mocks.showOpenDialogSync = jest.spyOn(dialog, 'showOpenDialogSync').mockImplementation(() => true); - mocks.showMessageBox = jest.spyOn(dialog, 'showMessageBox').mockImplementation(() =>{ }); - mocks.showMessageBoxSync = jest.spyOn(dialog, 'showMessageBoxSync').mockImplementation(() => 0); - mocks.export = jest.spyOn(importExport, 'importDatabaseFromFile').mockImplementation(() => ({ + const showOpenDialogSyncStub = stub(dialog, 'showOpenDialogSync').returns(true); + const showMessageBoxSyncStub = stub(dialog, 'showMessageBoxSync').returns(0); + importExportMock.mock('importDatabaseFromFile', stub().returns({ result: false, failed: 0 })); getEditMenuTemplate(mainWindow)[8].click(); - expect(mocks.showOpenDialogSync).toHaveBeenCalledTimes(1); - expect(mocks.showMessageBoxSync).toHaveBeenCalledTimes(2); - expect(mocks.showMessageBox).toHaveBeenCalledTimes(0); - expect(mocks.export).toHaveBeenCalledTimes(1); + assert.strictEqual(showOpenDialogSyncStub.calledOnce, true); + assert.strictEqual(showMessageBoxSyncStub.calledTwice, true); + assert.strictEqual(showMessageBoxStub.notCalled, true); + assert.strictEqual(importExportMock.getMock('importDatabaseFromFile').calledOnce, true); + showOpenDialogSyncStub.restore(); + showMessageBoxSyncStub.restore(); }); it('Should not show dialog for importing db', () => { - mocks.showOpenDialogSync = jest.spyOn(dialog, 'showOpenDialogSync').mockImplementation(() => false); - mocks.showMessageBox = jest.spyOn(dialog, 'showMessageBox').mockImplementation(() =>{ }); - mocks.showMessageBoxSync = jest.spyOn(dialog, 'showMessageBoxSync').mockImplementation(() => 1); - mocks.export = jest.spyOn(importExport, 'importDatabaseFromFile').mockImplementation(() =>{ }); + const showOpenDialogSyncStub = stub(dialog, 'showOpenDialogSync').returns(false); + const showMessageBoxSyncStub = stub(dialog, 'showMessageBoxSync').returns(1); + importExportMock.mock('importDatabaseFromFile', stub()); getEditMenuTemplate()[8].click(); - expect(mocks.showOpenDialogSync).toHaveBeenCalledTimes(1); - expect(mocks.showMessageBoxSync).toHaveBeenCalledTimes(0); - expect(mocks.showMessageBox).toHaveBeenCalledTimes(0); - expect(mocks.export).toHaveBeenCalledTimes(0); + assert.strictEqual(showOpenDialogSyncStub.calledOnce, true); + assert.strictEqual(showMessageBoxSyncStub.notCalled, true); + assert.strictEqual(showMessageBoxStub.notCalled, true); + assert.strictEqual(importExportMock.getMock('importDatabaseFromFile').notCalled, true); + showOpenDialogSyncStub.restore(); + showMessageBoxSyncStub.restore(); }); it('Should not show dialog for importing db', () => { - mocks.showOpenDialogSync = jest.spyOn(dialog, 'showOpenDialogSync').mockImplementation(() => true); - mocks.showMessageBox = jest.spyOn(dialog, 'showMessageBox').mockImplementation(() =>{ }); - mocks.showMessageBoxSync = jest.spyOn(dialog, 'showMessageBoxSync').mockImplementation(() => 1); - mocks.export = jest.spyOn(importExport, 'importDatabaseFromFile').mockImplementation(() =>{ }); + const showOpenDialogSyncStub = stub(dialog, 'showOpenDialogSync').returns(true); + const showMessageBoxSyncStub = stub(dialog, 'showMessageBoxSync').returns(1); + importExportMock.mock('importDatabaseFromFile', stub()); getEditMenuTemplate()[8].click(); - expect(mocks.showOpenDialogSync).toHaveBeenCalledTimes(1); - expect(mocks.showMessageBoxSync).toHaveBeenCalledTimes(1); - expect(mocks.showMessageBox).toHaveBeenCalledTimes(0); - expect(mocks.export).toHaveBeenCalledTimes(0); + assert.strictEqual(showOpenDialogSyncStub.calledOnce, true); + assert.strictEqual(showMessageBoxSyncStub.calledOnce, true); + assert.strictEqual(showMessageBoxStub.notCalled, true); + assert.strictEqual(importExportMock.getMock('importDatabaseFromFile').notCalled, true); + showOpenDialogSyncStub.restore(); + showMessageBoxSyncStub.restore(); }); it('Should not show dialog for clearing db', () => { - mocks.showMessageBox = jest.spyOn(dialog, 'showMessageBox').mockImplementation(() =>{ }); - mocks.showMessageBoxSync = jest.spyOn(dialog, 'showMessageBoxSync').mockImplementation(() => 0); + const showMessageBoxSyncStub = stub(dialog, 'showMessageBoxSync').returns(0); getEditMenuTemplate()[9].click(); - expect(mocks.showMessageBoxSync).toHaveBeenCalledTimes(1); - expect(mocks.showMessageBox).toHaveBeenCalledTimes(0); + assert.strictEqual(showMessageBoxSyncStub.calledOnce, true); + assert.strictEqual(showMessageBoxStub.notCalled, true); + showMessageBoxSyncStub.restore(); }); it('Should not show dialog for clearing db', () => @@ -564,27 +557,27 @@ describe('menus.js', () => } } }; - mocks.store = jest.spyOn(ElectronStore.prototype, 'clear').mockImplementation(() => {}); - mocks.showMessageBox = jest.spyOn(dialog, 'showMessageBox').mockImplementation(() =>{ }); - mocks.showMessageBoxSync = jest.spyOn(dialog, 'showMessageBoxSync').mockImplementation(() => 1); + const clearStoreStub = stub(Store.prototype, 'clear'); + const showMessageBoxSyncStub = stub(dialog, 'showMessageBoxSync').returns(1); getEditMenuTemplate(mainWindow)[9].click(); - expect(mocks.showMessageBoxSync).toHaveBeenCalledTimes(1); - expect(mocks.showMessageBox).toHaveBeenCalledTimes(1); - expect(mocks.store).toHaveBeenCalledTimes(3); + assert.strictEqual(showMessageBoxSyncStub.calledOnce, true); + assert.strictEqual(showMessageBoxStub.calledOnce, true); + assert.strictEqual(clearStoreStub.calledThrice, true); + clearStoreStub.restore(); + showMessageBoxSyncStub.restore(); }); - }); - afterEach(() => - { - for (const mock of Object.values(mocks)) + after(() => { - mock.mockClear(); - } + showMessageBoxStub.restore(); + }); }); - afterAll(() => + afterEach(() => { - jest.restoreAllMocks(); + importExportMock.restoreAll(); + notificationMock.restoreAll(); + updateManagerMock.restoreAll(); + windowsMock.restoreAll(); }); - }); \ No newline at end of file