Skip to content

Commit

Permalink
test: add more unit tests (#595)
Browse files Browse the repository at this point in the history
* refactor(lib): initPlugin and getSettings now are more testable and plugin-agnostic

Now they don't depend on the plugin import statement

* test(mocks): added new mock functions

* refactor(lib): separate concepts of get config and read config

This makes `get` function more testable

* test: added getUserSettings test

* refactor(dirs): moved test files to __tests__ folders

* test: added more tests

* test: add coverage
  • Loading branch information
dubisdev committed Sep 12, 2022
1 parent 739ece6 commit 93d6444
Show file tree
Hide file tree
Showing 16 changed files with 261 additions and 66 deletions.
1 change: 1 addition & 0 deletions __mocks__/@electron/remote.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
app: {
getPath: () => '',
getLocale: () => '',
},
}
1 change: 1 addition & 0 deletions __mocks__/electron.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
app: {
getPath: jest.fn(),
getLocale: jest.fn(),
},
ipcRenderer: {
on: jest.fn(),
Expand Down
9 changes: 4 additions & 5 deletions app/background/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ on('initializePluginAsync', ({ name }) => {
console.group(`Initialize async plugin ${name}`)

try {
const { initializeAsync } = plugins[name]
? plugins[name]
: window.require(`${modulesDirectory}/${name}`)
const plugin = plugins[name] || window.require(`${modulesDirectory}/${name}`)
const { initializeAsync } = plugin

if (!initializeAsync) {
console.log('no `initializeAsync` function, skipped')
Expand All @@ -29,11 +28,11 @@ on('initializePluginAsync', ({ name }) => {
console.log('Done! Sending data back to main window')
// Send message back to main window with initialization result
send('plugin.message', { name, data })
}, pluginSettings.getUserSettings(name))
}, pluginSettings.getUserSettings(plugin, name))
} catch (err) { console.log('Failed', err) }

console.groupEnd()
})

// Handle `reload` rpc event and reload window
on('reload', () => location.reload())
on('reload', () => window.location.reload())
32 changes: 32 additions & 0 deletions app/lib/__tests__/loadThemes.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import themesLoader from '../loadThemes'

const productionThemes = [
{
value: '../dist/main/css/themes/light.css',
label: 'Light'
},
{
value: '../dist/main/css/themes/dark.css',
label: 'Dark'
}
]

const developmentThemes = [
{
value: 'http://localhost:3000/dist/main/css/themes/light.css',
label: 'Light'
},
{
value: 'http://localhost:3000/dist/main/css/themes/dark.css',
label: 'Dark'
}
]

test('returns themes for production', () => {
expect(themesLoader()).toEqual(productionThemes)
})

test('returns themes for development', () => {
process.env.NODE_ENV = 'development'
expect(themesLoader()).toEqual(developmentThemes)
})
19 changes: 11 additions & 8 deletions app/lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ const readConfig = () => {
try {
return JSON.parse(fs.readFileSync(CONFIG_FILE).toString())
} catch (err) {
const config = defaultSettings()

if (err.code !== 'ENOENT') {
console.error('Error reading config file', err)
return config
}

if (process.env.NODE_ENV === 'test') return config

fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2))
return defaultSettings()
}
}
Expand All @@ -57,14 +67,7 @@ const readConfig = () => {
* @return {Any}
*/
const get = (key) => {
let config

if (!fs.existsSync(CONFIG_FILE)) {
// Save default config to local storage
config = defaultSettings()
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2))
} else { config = readConfig() }

const config = readConfig()
return config[key]
}

Expand Down
29 changes: 29 additions & 0 deletions app/lib/initPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { send } from 'lib/rpc'
import { settings as pluginSettings } from 'lib/plugins'

/**
* Initialices plugin sync and/or async by calling the `initialize` and `initializeAsync` functions
* @param {Object} plugin A plugin object
* @param {string} name The name entry in the plugin package.json
*/
const initPlugin = (plugin, name) => {
const { initialize, initializeAsync } = plugin

// Foreground plugin initialization
if (initialize) {
console.log('Initialize sync plugin', name)
try {
initialize(pluginSettings.getUserSettings(plugin, name))
} catch (e) {
console.error(`Failed to initialize plugin: ${name}`, e)
}
}

// Background plugin initialization
if (initializeAsync) {
console.log('Initialize async plugin', name)
send('initializePluginAsync', { name })
}
}

export default initPlugin
26 changes: 4 additions & 22 deletions app/lib/initializePlugins.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,9 @@
import { on, send } from 'lib/rpc'
import { on } from 'lib/rpc'
import plugins from 'plugins'
import { settings as pluginSettings } from 'lib/plugins'

export const initializePlugin = (name) => {
const { initialize, initializeAsync } = plugins[name]

if (initialize) {
// Foreground plugin initialization
try {
initialize(pluginSettings.getUserSettings(name))
} catch (e) {
console.error(`Failed to initialize plugin: ${name}`, e)
}
}

if (initializeAsync) {
// Background plugin initialization
send('initializePluginAsync', { name })
}
}
import initPlugin from './initPlugin'

/**
* RPC-call for plugins initializations
* Starts listening for `initializePlugin` events and initializes all plugins
*/
export default () => {
// Start listening for replies from plugin async initializers
Expand All @@ -30,5 +12,5 @@ export default () => {
if (plugin.onMessage) plugin.onMessage(data)
})

Object.keys(plugins).forEach(initializePlugin)
Object.keys(plugins).forEach((name) => initPlugin(plugins[name], name))
}
21 changes: 21 additions & 0 deletions app/lib/plugins/settings/__tests__/get.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import getUserSettings from '../get'

const plugin = {
settings: {
test_setting1: {
type: 'string',
defaultValue: 'test',
},
test_setting2: {
type: 'number',
defaultValue: 1,
},
}
}

describe('Test getUserSettings', () => {
it('returns valid settings object', () => {
expect(getUserSettings(plugin, 'test-plugin'))
.toEqual({ test_setting1: 'test', test_setting2: 1 })
})
})
84 changes: 84 additions & 0 deletions app/lib/plugins/settings/__tests__/validate.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import validate from '../validate'

const validSettings = {
option1: {
description: 'Just a test description',
type: 'option',
options: ['option_1', 'option_2'],
},
option2: {
description: 'Just a test description',
type: 'number',
defaultValue: 0
},
option3: {
description: 'Just a test description',
type: 'number',
defaultValue: 0
},
option4: {
description: 'Just a test description',
type: 'bool'
},
option5: {
description: 'Just a test description',
type: 'string',
defaultValue: 'test'
}
}

const invalidSettingsNoOptionsProvided = {
option1: {
description: 'Just a test description',
type: 'option',
options: [],
}
}

const invalidSettingsInvalidType = {
option1: {
description: 'Just a test description',
type: 'test'
}
}

describe('Validate settings function', () => {
it('returns true when plugin has no settings field', () => {
const plugin = {
fn: () => {}
}
expect(validate(plugin)).toEqual(true)
})

it('returns true when plugin has empty settings field', () => {
const plugin = {
fn: () => {},
settings: {}
}
expect(validate(plugin)).toEqual(true)
})

it('returns true when plugin has valid settings', () => {
const plugin = {
fn: () => {},
settings: validSettings
}
expect(validate(plugin)).toEqual(true)
})

it('returns false when option type is options and no options provided', () => {
const plugin = {
fn: () => {},
settings: invalidSettingsNoOptionsProvided
}
expect(validate(plugin)).toEqual(false)
})

it('returns false when option type is incorrect', () => {
const plugin = {
fn: () => {},
settings: invalidSettingsInvalidType
}
expect(validate(plugin)).toEqual(false)
})
})
31 changes: 23 additions & 8 deletions app/lib/plugins/settings/get.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
import config from 'lib/config'
import plugins from 'plugins'

const getSettings = pluginName => config.get('plugins')[pluginName] || {}
/**
* Returns the settings established by the user and previously saved in the config file
* @param {string} pluginName The name entry of the plugin package.json
* @returns An object with keys and values of the **stored** plugin settings
*/
const getExistingSettings = (pluginName) => config.get('plugins')[pluginName] || {}

const getUserSettings = (pluginName) => {
const settings = getSettings(pluginName)
/**
* Returns the sum of the default settings and the user settings
* We use packageJsonName to avoid conflicts with plugins that export
* a different name from the bundle. Two plugins can export the same name
* but can't have the same package.json name
* @param {Object} plugin
* @param {string} packageJsonName
* @returns An object with keys and values of the plugin settings
*/
const getUserSettings = (plugin, packageJsonName) => {
const userSettings = {}
const existingSettings = getExistingSettings(packageJsonName)
const { settings: pluginSettings } = plugin

if (plugins[pluginName].settings) {
if (pluginSettings) {
// Provide default values if nothing is set by user
Object.keys(plugins[pluginName].settings).forEach((key) => {
settings[key] = settings[key] || plugins[pluginName].settings[key].defaultValue
Object.keys(pluginSettings).forEach((key) => {
userSettings[key] = existingSettings[key] || pluginSettings[key].defaultValue
})
}

return settings
return userSettings
}

export default getUserSettings
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
RESET,
} from 'main/constants/actionTypes'

import * as actions from './search'
import * as actions from '../search'

describe('reset', () => {
it('returns valid action', () => {
Expand Down
27 changes: 27 additions & 0 deletions app/main/actions/__tests__/statusBar.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* @jest-environment jsdom
*/

import {
SET_STATUS_BAR_TEXT
} from 'main/constants/actionTypes'

import * as actions from '../statusBar'

describe('reset', () => {
it('returns valid action', () => {
expect(actions.reset()).toEqual({
type: SET_STATUS_BAR_TEXT,
payload: null
})
})
})

describe('setValue', () => {
it('returns valid action when value passed', () => {
expect(actions.setValue('test value')).toEqual({
type: SET_STATUS_BAR_TEXT,
payload: 'test value'
})
})
})
Loading

0 comments on commit 93d6444

Please sign in to comment.