From 8da1765e3fe799f9794467be3967490ba936271b Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Mon, 26 Aug 2019 10:26:29 +0200 Subject: [PATCH] feat: write some unit tests (#1038) License: MIT Signed-off-by: Henrique Dias --- package-lock.json | 149 +++++++++++++++++++++++++++++ package.json | 4 +- test/create-toggler.spec.js | 157 +++++++++++++++++++++++++++++++ test/dock.spec.js | 111 ++++++++++++++++++++++ test/{spec.js => launch.spec.js} | 0 test/mocks/electron.js | 23 +++++ test/mocks/store.js | 20 ++++ test/mocks/webui.js | 9 ++ 8 files changed, 472 insertions(+), 1 deletion(-) create mode 100644 test/create-toggler.spec.js create mode 100644 test/dock.spec.js rename test/{spec.js => launch.spec.js} (100%) create mode 100644 test/mocks/electron.js create mode 100644 test/mocks/store.js create mode 100644 test/mocks/webui.js diff --git a/package-lock.json b/package-lock.json index 2234f063b..1fea9095b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1404,6 +1404,42 @@ "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", "dev": true }, + "@sinonjs/commons": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.4.0.tgz", + "integrity": "sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/formatio": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", + "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } + }, + "@sinonjs/samsam": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.2.tgz", + "integrity": "sha512-ILO/rR8LfAb60Y1Yfp9vxfYAASK43NFC2mLzpvLUbCQY/Qu8YwReboseu8aheCEkyElZF2L2T9mHcR2bgdvZyA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.0.2", + "array-from": "^2.1.1", + "lodash": "^4.17.11" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@svgr/babel-plugin-add-jsx-attribute": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz", @@ -1832,6 +1868,12 @@ "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", "dev": true }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, "array-includes": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", @@ -4841,6 +4883,16 @@ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, + "fill-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", + "dev": true, + "requires": { + "is-object": "~1.0.1", + "merge-descriptors": "~1.0.0" + } + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -6672,6 +6724,12 @@ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" }, + "is-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", + "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", + "dev": true + }, "is-path-inside": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", @@ -6965,6 +7023,12 @@ "object.assign": "^4.1.0" } }, + "just-extend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", + "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", + "dev": true + }, "just-kebab-case": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-kebab-case/-/just-kebab-case-1.1.0.tgz", @@ -7199,6 +7263,12 @@ "triple-beam": "^1.3.0" } }, + "lolex": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", + "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", + "dev": true + }, "looper": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/looper/-/looper-3.0.0.tgz", @@ -7372,6 +7442,12 @@ } } }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -7594,6 +7670,12 @@ } } }, + "module-not-found-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", + "dev": true + }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -7745,6 +7827,19 @@ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, + "nise": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.1.tgz", + "integrity": "sha512-edFWm0fsFG2n318rfEnKlTZTkjlbVOFF9XIA+fj+Ed+Qz1laYW2lobwavWoMzGrYDHH1EpiNJgDfvGnkZztR/g==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^4.1.0", + "path-to-regexp": "^1.7.0" + } + }, "node-environment-flags": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", @@ -8363,6 +8458,23 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", @@ -8755,6 +8867,28 @@ "varint": "^5.0.0" } }, + "proxyquire": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", + "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", + "dev": true, + "requires": { + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.1", + "resolve": "^1.11.1" + }, + "dependencies": { + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -9675,6 +9809,21 @@ } } }, + "sinon": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.4.1.tgz", + "integrity": "sha512-7s9buHGHN/jqoy/v4bJgmt0m1XEkCEd/tqdHXumpBp0JSujaT4Ng84JU5wDdK4E85ZMq78NuDe0I3NAqXY8TFg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.2", + "diff": "^3.5.0", + "lolex": "^4.2.0", + "nise": "^1.5.1", + "supports-color": "^5.5.0" + } + }, "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", diff --git a/package.json b/package.json index fdf6839f6..ea18ecdb4 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "start": "cross-env NODE_ENV=development electron -r @babel/register src/index.js", "lint": "standard", - "test": "cross-env NODE_ENV=test mocha", + "test": "cross-env NODE_ENV=test mocha -r @babel/register", "postinstall": "run-s install-app-deps build:webui", "install-app-deps": "electron-builder install-app-deps", "clean:webui": "shx rm -rf assets/webui/", @@ -62,9 +62,11 @@ "mocha": "^6.2.0", "npm-run-all": "^4.1.5", "pre-commit": "^1.2.2", + "proxyquire": "^2.1.3", "request": "^2.88.0", "request-progress": "^3.0.0", "shx": "^0.3.2", + "sinon": "^7.4.1", "spectron": "^8.0.0", "standard": "^14.0.2", "tar": "^4.4.10", diff --git a/test/create-toggler.spec.js b/test/create-toggler.spec.js new file mode 100644 index 000000000..5d2b92eeb --- /dev/null +++ b/test/create-toggler.spec.js @@ -0,0 +1,157 @@ +/* eslint-env mocha */ + +import sinon from 'sinon' +import chai, { expect } from 'chai' +import dirtyChai from 'dirty-chai' +import mockElectron from './mocks/electron' +import mockStore from './mocks/store' +import mockWebUI from './mocks/webui' + +const proxyquire = require('proxyquire').noCallThru() +chai.use(dirtyChai) + +describe('Create toggler', () => { + const option = 'OPT' + let electron, store, webui, createToggler + + beforeEach(() => { + electron = mockElectron() + store = mockStore() + webui = mockWebUI() + createToggler = proxyquire('../src/create-toggler', { + electron: electron, + './common/store': store + }).default + }) + + it('activate option with success', (done) => { + const activate = sinon.stub().returns(true) + createToggler({ webui: webui }, option, activate) + electron.ipcMain.emit('config.toggle', null, option) + + setImmediate(() => { + expect(store.get.callCount).to.equal(1) + expect(activate.callCount).to.equal(1) + expect(store.set.callCount).to.equal(1) + + const args = webui.webContents.send.lastCall.args + + expect(args[0]).to.equal('config.changed') + expect(args[1]).to.deep.equal({ + changed: 'OPT', + config: { + OPT: true + }, + success: true + }) + + done() + }) + }) + + it('activate option with error', (done) => { + const activate = sinon.stub().returns(false) + createToggler({ webui: webui }, option, activate) + electron.ipcMain.emit('config.toggle', null, option) + + setImmediate(() => { + expect(store.get.callCount).to.equal(1) + expect(activate.callCount).to.equal(1) + expect(store.set.callCount).to.equal(0) + + const args = webui.webContents.send.lastCall.args + + expect(args[0]).to.equal('config.changed') + expect(args[1]).to.deep.equal({ + changed: 'OPT', + config: {}, + success: false + }) + + done() + }) + }) + + it('disable option with success', (done) => { + const activate = sinon.stub().returns(true) + store.set(option, true) + + createToggler({ webui: webui }, option, activate) + electron.ipcMain.emit('config.toggle', null, option) + + setImmediate(() => { + expect(store.get.callCount).to.equal(1) + expect(activate.callCount).to.equal(1) + expect(store.set.callCount).to.equal(2) + + const args = webui.webContents.send.lastCall.args + + expect(args[0]).to.equal('config.changed') + expect(args[1]).to.deep.equal({ + changed: 'OPT', + config: { + OPT: false + }, + success: true + }) + + done() + }) + }) + + it('disable option with error', (done) => { + const activate = sinon.stub().returns(false) + store.set(option, true) + + createToggler({ webui: webui }, option, activate) + electron.ipcMain.emit('config.toggle', null, option) + + setImmediate(() => { + expect(store.get.callCount).to.equal(1) + expect(activate.callCount).to.equal(1) + expect(store.set.callCount).to.equal(1) + + const args = webui.webContents.send.lastCall.args + + expect(args[0]).to.equal('config.changed') + expect(args[1]).to.deep.equal({ + changed: 'OPT', + config: { + OPT: true + }, + success: false + }) + + done() + }) + }) + + it('enable and disable option with success', (done) => { + const activate = sinon.stub().returns(true) + + createToggler({ webui: webui }, option, activate) + electron.ipcMain.emit('config.toggle', null, option) + electron.ipcMain.emit('config.toggle', null, option) + + setImmediate(() => { + expect(store.get.callCount).to.equal(2) + expect(activate.callCount).to.equal(2) + expect(store.set.callCount).to.equal(2) + done() + }) + }) + + it('do not trigger anything on different option', (done) => { + const activate = sinon.spy() + + createToggler({ webui: webui }, option, activate) + electron.ipcMain.emit('config.toggle', null, 'ANOTHER_OPTION') + + setImmediate(() => { + expect(store.get.callCount).to.equal(0) + expect(activate.callCount).to.equal(0) + expect(store.set.callCount).to.equal(0) + done() + }) + }) +}) diff --git a/test/dock.spec.js b/test/dock.spec.js new file mode 100644 index 000000000..48adb6141 --- /dev/null +++ b/test/dock.spec.js @@ -0,0 +1,111 @@ +/* eslint-env mocha */ + +import sinon from 'sinon' +import chai, { expect } from 'chai' +import dirtyChai from 'dirty-chai' +import mockElectron from './mocks/electron' + +const proxyquire = require('proxyquire') + .noCallThru() + .noPreserveCache() + +chai.use(dirtyChai) + +describe('Dock', () => { + it('show dock succeeds with dock (macOS)', () => { + const electron = mockElectron({ withDock: true }) + const { show } = proxyquire('../src/dock', { electron }).default + show() + expect(electron.app.dock.show.callCount).to.equal(1) + }) + + it('show dock succeeds without dock (other OSes)', () => { + const electron = mockElectron() + const { show } = proxyquire('../src/dock', { electron }).default + show() + }) + + it('hide dock succeeds with dock and no windows (macOS)', () => { + const electron = mockElectron({ withDock: true }) + electron.BrowserWindow.getAllWindows.returns([]) + const { hide } = proxyquire('../src/dock', { electron }).default + hide() + expect(electron.app.dock.hide.callCount).to.equal(1) + }) + + it('hide dock succeeds with dock and no visible windows (macOS)', () => { + const electron = mockElectron({ withDock: true }) + const windows = [ + { isVisible: sinon.stub().returns(false) }, + { isVisible: sinon.stub().returns(false) } + ] + electron.BrowserWindow.getAllWindows.returns(windows) + const { hide } = proxyquire('../src/dock', { electron }).default + hide() + expect(windows[0].isVisible.callCount).to.equal(1) + expect(windows[1].isVisible.callCount).to.equal(1) + expect(electron.app.dock.hide.callCount).to.equal(1) + }) + + it('hide dock succeeds with dock and with visible windows (macOS)', () => { + const electron = mockElectron({ withDock: true }) + const windows = [ + { isVisible: sinon.stub().returns(true) }, + { isVisible: sinon.stub().returns(false) } + ] + electron.BrowserWindow.getAllWindows.returns(windows) + const { hide } = proxyquire('../src/dock', { electron }).default + hide() + expect(windows[0].isVisible.callCount).to.equal(1) + expect(windows[1].isVisible.callCount).to.equal(1) + expect(electron.app.dock.hide.callCount).to.equal(0) + }) + + it('hide dock succeeds without dock (other OSes)', () => { + const electron = mockElectron() + const { hide } = proxyquire('../src/dock', { electron }).default + hide() + }) + + it('runs async function with dock (macOS)', async () => { + const electron = mockElectron({ withDock: true }) + electron.BrowserWindow.getAllWindows.returns([]) + const { run } = proxyquire('../src/dock', { electron }).default + const fn = sinon.stub().resolves(5) + const res = await run(fn) + expect(res).to.equal(5) + expect(electron.app.dock.show.callCount).to.equal(1) + expect(electron.app.dock.hide.callCount).to.equal(1) + expect(electron.app.dock.show.calledBefore(electron.app.dock.hide)).to.equal(true) + }) + + it('runs async function without dock (other OSes)', async () => { + const electron = mockElectron() + electron.BrowserWindow.getAllWindows.returns([]) + const { run } = proxyquire('../src/dock', { electron }).default + const fn = sinon.stub().resolves(5) + const res = await run(fn) + expect(res).to.equal(5) + }) + + it('runs sync function with dock (macOS)', () => { + const electron = mockElectron({ withDock: true }) + electron.BrowserWindow.getAllWindows.returns([]) + const { runSync } = proxyquire('../src/dock', { electron }).default + const fn = sinon.stub().returns(5) + const res = runSync(fn) + expect(res).to.equal(5) + expect(electron.app.dock.show.callCount).to.equal(1) + expect(electron.app.dock.hide.callCount).to.equal(1) + expect(electron.app.dock.show.calledBefore(electron.app.dock.hide)).to.equal(true) + }) + + it('runs sync function without dock (other OSes)', () => { + const electron = mockElectron() + electron.BrowserWindow.getAllWindows.returns([]) + const { runSync } = proxyquire('../src/dock', { electron }).default + const fn = sinon.stub().returns(5) + const res = runSync(fn) + expect(res).to.equal(5) + }) +}) diff --git a/test/spec.js b/test/launch.spec.js similarity index 100% rename from test/spec.js rename to test/launch.spec.js diff --git a/test/mocks/electron.js b/test/mocks/electron.js new file mode 100644 index 000000000..c88237872 --- /dev/null +++ b/test/mocks/electron.js @@ -0,0 +1,23 @@ +import { EventEmitter } from 'events' +import sinon from 'sinon' + +export default function mockElectron (opts = {}) { + opts.withDock = opts.withDock || false + + const electron = { + ipcMain: new EventEmitter(), + BrowserWindow: { + getAllWindows: sinon.stub() + }, + app: {} + } + + if (opts.withDock) { + electron.app.dock = { + show: sinon.spy(), + hide: sinon.spy() + } + } + + return electron +} diff --git a/test/mocks/store.js b/test/mocks/store.js new file mode 100644 index 000000000..e3f635886 --- /dev/null +++ b/test/mocks/store.js @@ -0,0 +1,20 @@ +import sinon from 'sinon' + +export default function mockStore () { + let store = {} + + return { + get: sinon.stub().callsFake((key, def) => { + return typeof store[key] !== 'undefined' ? store[key] : def + }), + set: sinon.stub().callsFake((key, val) => { + store[key] = val + }), + clear: () => { + store = {} + }, + get store () { + return store + } + } +} diff --git a/test/mocks/webui.js b/test/mocks/webui.js new file mode 100644 index 000000000..1c14fc30b --- /dev/null +++ b/test/mocks/webui.js @@ -0,0 +1,9 @@ +import sinon from 'sinon' + +export default function mockWebUI () { + return { + webContents: { + send: sinon.spy() + } + } +}