From c0eb9bd1e91998b01b4d0c48982951319201e8a6 Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Thu, 9 Jan 2020 15:00:58 +0100 Subject: [PATCH] Convert settings.js to typescript Change-type: patch --- lib/gui/app/app.js | 1 + lib/gui/app/models/settings.js | 234 ---------------------- lib/gui/app/models/settings.ts | 142 +++++++++++++ lib/gui/app/models/store.js | 1 + lib/gui/app/modules/image-writer.js | 1 + lib/gui/etcher.js | 1 + tests/gui/models/settings.spec.js | 85 ++++---- tests/gui/modules/progress-status.spec.js | 1 + 8 files changed, 190 insertions(+), 276 deletions(-) delete mode 100644 lib/gui/app/models/settings.js create mode 100644 lib/gui/app/models/settings.ts diff --git a/lib/gui/app/app.js b/lib/gui/app/app.js index 935d10fe..0914b5fa 100644 --- a/lib/gui/app/app.js +++ b/lib/gui/app/app.js @@ -32,6 +32,7 @@ const messages = require('../../shared/messages') const store = require('./models/store') const packageJSON = require('../../../package.json') const flashState = require('./models/flash-state') +// eslint-disable-next-line node/no-missing-require const settings = require('./models/settings') // eslint-disable-next-line node/no-missing-require const windowProgress = require('./os/window-progress') diff --git a/lib/gui/app/models/settings.js b/lib/gui/app/models/settings.js deleted file mode 100644 index e3158ce2..00000000 --- a/lib/gui/app/models/settings.js +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright 2016 balena.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -'use strict' - -/** - * @module Etcher.Models.Settings - */ - -const _ = require('lodash') -const Bluebird = require('bluebird') -// eslint-disable-next-line node/no-missing-require -const localSettings = require('./local-settings') -// eslint-disable-next-line node/no-missing-require -const errors = require('../../../shared/errors') -const packageJSON = require('../../../../package.json') -const debug = require('debug')('etcher:models:settings') - -/** - * @summary Default settings - * @constant - * @type {Object} - */ -const DEFAULT_SETTINGS = { - unsafeMode: false, - errorReporting: true, - unmountOnSuccess: true, - validateWriteOnSuccess: true, - trim: false, - updatesEnabled: packageJSON.updates.enabled && !_.includes([ 'rpm', 'deb' ], packageJSON.packageType), - lastSleptUpdateNotifier: null, - lastSleptUpdateNotifierVersion: null, - desktopNotifications: true -} - -/** - * @summary Settings state - * @type {Object} - * @private - */ -let settings = _.cloneDeep(DEFAULT_SETTINGS) - -/** - * @summary Reset settings to their default values - * @function - * @public - * - * @returns {Promise} - * - * @example - * settings.reset().then(() => { - * console.log('Done!'); - * }); - */ -exports.reset = () => { - debug('reset') - - // TODO: Remove default settings from config file (?) - settings = _.cloneDeep(DEFAULT_SETTINGS) - return localSettings.writeAll(settings) -} - -/** - * @summary Extend the current settings - * @function - * @public - * - * @param {Object} value - value - * @returns {Promise} - * - * @example - * settings.assign({ - * foo: 'bar' - * }).then(() => { - * console.log('Done!'); - * }); - */ -exports.assign = (value) => { - debug('assign', value) - if (_.isNil(value)) { - return Bluebird.reject(errors.createError({ - title: 'Missing settings' - })) - } - - if (!_.isPlainObject(value)) { - return Bluebird.reject(errors.createError({ - title: 'Settings must be an object' - })) - } - - const newSettings = _.assign({}, settings, value) - - return localSettings.writeAll(newSettings) - .then((updatedSettings) => { - // NOTE: Only update in memory settings when successfully written - settings = updatedSettings - }) -} - -/** - * @summary Extend the application state with the local settings - * @function - * @public - * - * @returns {Promise} - * - * @example - * settings.load().then(() => { - * console.log('Done!'); - * }); - */ -exports.load = () => { - debug('load') - return localSettings.readAll().then((loadedSettings) => { - return _.assign(settings, loadedSettings) - }) -} - -/** - * @summary Set a setting value - * @function - * @public - * - * @param {String} key - setting key - * @param {*} value - setting value - * @returns {Promise} - * - * @example - * settings.set('unmountOnSuccess', true).then(() => { - * console.log('Done!'); - * }); - */ -exports.set = (key, value) => { - debug('set', key, value) - if (_.isNil(key)) { - return Bluebird.reject(errors.createError({ - title: 'Missing setting key' - })) - } - - if (!_.isString(key)) { - return Bluebird.reject(errors.createError({ - title: `Invalid setting key: ${key}` - })) - } - - const previousValue = settings[key] - - settings[key] = value - - return localSettings.writeAll(settings) - .catch((error) => { - // Revert to previous value if persisting settings failed - settings[key] = previousValue - throw error - }) -} - -/** - * @summary Get a setting value - * @function - * @public - * - * @param {String} key - setting key - * @returns {*} setting value - * - * @example - * const value = settings.get('unmountOnSuccess'); - */ -exports.get = (key) => { - return _.cloneDeep(_.get(settings, [ key ])) -} - -/** - * @summary Check if setting value exists - * @function - * @public - * - * @param {String} key - setting key - * @returns {Boolean} exists - * - * @example - * const hasValue = settings.has('unmountOnSuccess'); - */ -exports.has = (key) => { - /* eslint-disable no-eq-null */ - return settings[key] != null -} - -/** - * @summary Get all setting values - * @function - * @public - * - * @returns {Object} all setting values - * - * @example - * const allSettings = settings.getAll(); - * console.log(allSettings.unmountOnSuccess); - */ -exports.getAll = () => { - debug('getAll') - return _.cloneDeep(settings) -} - -/** - * @summary Get the default setting values - * @function - * @public - * - * @returns {Object} all setting values - * - * @example - * const defaults = settings.getDefaults(); - * console.log(defaults.unmountOnSuccess); - */ -exports.getDefaults = () => { - debug('getDefaults') - return _.cloneDeep(DEFAULT_SETTINGS) -} diff --git a/lib/gui/app/models/settings.ts b/lib/gui/app/models/settings.ts new file mode 100644 index 00000000..bf6db9c7 --- /dev/null +++ b/lib/gui/app/models/settings.ts @@ -0,0 +1,142 @@ +/* + * Copyright 2016 balena.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as _debug from 'debug'; +import * as _ from 'lodash'; + +import * as packageJSON from '../../../../package.json'; +import * as errors from '../../../shared/errors'; +import { Dictionary } from '../../../shared/utils'; +import * as localSettings from './local-settings'; + +const debug = _debug('etcher:models:settings'); + +const DEFAULT_SETTINGS: Dictionary = { + unsafeMode: false, + errorReporting: true, + unmountOnSuccess: true, + validateWriteOnSuccess: true, + trim: false, + updatesEnabled: + packageJSON.updates.enabled && + !_.includes(['rpm', 'deb'], packageJSON.packageType), + lastSleptUpdateNotifier: null, + lastSleptUpdateNotifierVersion: null, + desktopNotifications: true, +}; + +let settings = _.cloneDeep(DEFAULT_SETTINGS); + +/** + * @summary Reset settings to their default values + */ +export async function reset(): Promise { + debug('reset'); + // TODO: Remove default settings from config file (?) + settings = _.cloneDeep(DEFAULT_SETTINGS); + return await localSettings.writeAll(settings); +} + +/** + * @summary Extend the current settings + */ +export async function assign(value: Dictionary): Promise { + debug('assign', value); + if (_.isNil(value)) { + throw errors.createError({ + title: 'Missing settings', + }); + } + + if (!_.isPlainObject(value)) { + throw errors.createError({ + title: 'Settings must be an object', + }); + } + + const newSettings = _.assign({}, settings, value); + + const updatedSettings = await localSettings.writeAll(newSettings); + // NOTE: Only update in memory settings when successfully written + settings = updatedSettings; +} + +/** + * @summary Extend the application state with the local settings + */ +export async function load(): Promise { + debug('load'); + const loadedSettings = await localSettings.readAll(); + _.assign(settings, loadedSettings); +} + +/** + * @summary Set a setting value + */ +export async function set(key: string, value: any): Promise { + debug('set', key, value); + if (_.isNil(key)) { + throw errors.createError({ + title: 'Missing setting key', + }); + } + + if (!_.isString(key)) { + throw errors.createError({ + title: `Invalid setting key: ${key}`, + }); + } + + const previousValue = settings[key]; + settings[key] = value; + try { + await localSettings.writeAll(settings); + } catch (error) { + // Revert to previous value if persisting settings failed + settings[key] = previousValue; + throw error; + } +} + +/** + * @summary Get a setting value + */ +export function get(key: string): any { + return _.cloneDeep(_.get(settings, [key])); +} + +/** + * @summary Check if setting value exists + */ +export function has(key: string): boolean { + return settings[key] != null; +} + +/** + * @summary Get all setting values + */ +export function getAll() { + debug('getAll'); + return _.cloneDeep(settings); +} + +/** + * @summary Get the default setting values + */ +export function getDefaults() { + debug('getDefaults'); + return _.cloneDeep(DEFAULT_SETTINGS); +} diff --git a/lib/gui/app/models/store.js b/lib/gui/app/models/store.js index f4938d96..5a75255c 100644 --- a/lib/gui/app/models/store.js +++ b/lib/gui/app/models/store.js @@ -30,6 +30,7 @@ const errors = require('../../../shared/errors') const fileExtensions = require('../../../shared/file-extensions') // eslint-disable-next-line node/no-missing-require const utils = require('../../../shared/utils') +// eslint-disable-next-line node/no-missing-require const settings = require('./settings') /** diff --git a/lib/gui/app/modules/image-writer.js b/lib/gui/app/modules/image-writer.js index 1a94481c..b28963d9 100644 --- a/lib/gui/app/modules/image-writer.js +++ b/lib/gui/app/modules/image-writer.js @@ -23,6 +23,7 @@ const os = require('os') const ipc = require('node-ipc') const electron = require('electron') const store = require('../models/store') +// eslint-disable-next-line node/no-missing-require const settings = require('../models/settings') const flashState = require('../models/flash-state') // eslint-disable-next-line node/no-missing-require diff --git a/lib/gui/etcher.js b/lib/gui/etcher.js index 3540f610..1887c842 100644 --- a/lib/gui/etcher.js +++ b/lib/gui/etcher.js @@ -26,6 +26,7 @@ const semver = require('semver') const EXIT_CODES = require('../shared/exit-codes') // eslint-disable-next-line node/no-missing-require const { buildWindowMenu } = require('./menu') +// eslint-disable-next-line node/no-missing-require const settings = require('./app/models/settings') // eslint-disable-next-line node/no-missing-require const analytics = require('./app/modules/analytics') diff --git a/tests/gui/models/settings.spec.js b/tests/gui/models/settings.spec.js index bf00d16d..44a8d57a 100644 --- a/tests/gui/models/settings.spec.js +++ b/tests/gui/models/settings.spec.js @@ -19,10 +19,21 @@ const m = require('mochainon') const _ = require('lodash') const Bluebird = require('bluebird') +// eslint-disable-next-line node/no-missing-require const settings = require('../../../lib/gui/app/models/settings') // eslint-disable-next-line node/no-missing-require const localSettings = require('../../../lib/gui/app/models/local-settings') +const checkError = async (promise, fn) => { + try { + await promise + } catch (error) { + fn(error) + return + } + throw new Error('Expected error was not thrown') +} + describe('Browser: settings', function () { beforeEach(function () { return settings.reset() @@ -74,11 +85,10 @@ describe('Browser: settings', function () { }) describe('.assign()', function () { - it('should throw if no settings', function (done) { - settings.assign().asCallback((error) => { + it('should throw if no settings', async function () { + await checkError(settings.assign(), (error) => { m.chai.expect(error).to.be.an.instanceof(Error) m.chai.expect(error.message).to.equal('Missing settings') - done() }) }) @@ -109,23 +119,19 @@ describe('Browser: settings', function () { }) }) - it('should not change the application state if storing to the local machine results in an error', function (done) { - settings.set('foo', 'bar').then(() => { + it('should not change the application state if storing to the local machine results in an error', async function () { + await settings.set('foo', 'bar') + m.chai.expect(settings.get('foo')).to.equal('bar') + + const localSettingsWriteAllStub = m.sinon.stub(localSettings, 'writeAll') + localSettingsWriteAllStub.returns(Bluebird.reject(new Error('localSettings error'))) + + await checkError(settings.assign({ foo: 'baz' }), (error) => { + m.chai.expect(error).to.be.an.instanceof(Error) + m.chai.expect(error.message).to.equal('localSettings error') + localSettingsWriteAllStub.restore() m.chai.expect(settings.get('foo')).to.equal('bar') - - const localSettingsWriteAllStub = m.sinon.stub(localSettings, 'writeAll') - localSettingsWriteAllStub.returns(Bluebird.reject(new Error('localSettings error'))) - - settings.assign({ - foo: 'baz' - }).asCallback((error) => { - m.chai.expect(error).to.be.an.instanceof(Error) - m.chai.expect(error.message).to.equal('localSettings error') - localSettingsWriteAllStub.restore() - m.chai.expect(settings.get('foo')).to.equal('bar') - done() - }) - }).catch(done) + }) }) }) @@ -161,27 +167,24 @@ describe('Browser: settings', function () { }) }) - it('should reject if no key', function (done) { - settings.set(null, true).asCallback((error) => { + it('should reject if no key', async function () { + await checkError(settings.set(null, true), (error) => { m.chai.expect(error).to.be.an.instanceof(Error) m.chai.expect(error.message).to.equal('Missing setting key') - done() }) }) - it('should throw if key is not a string', function (done) { - settings.set(1234, true).asCallback((error) => { + it('should throw if key is not a string', async function () { + await checkError(settings.set(1234, true), (error) => { m.chai.expect(error).to.be.an.instanceof(Error) m.chai.expect(error.message).to.equal('Invalid setting key: 1234') - done() }) }) - it('should throw if setting an array', function (done) { - settings.assign([ 1, 2, 3 ]).asCallback((error) => { + it('should throw if setting an array', async function () { + await checkError(settings.assign([ 1, 2, 3 ]), (error) => { m.chai.expect(error).to.be.an.instanceof(Error) m.chai.expect(error.message).to.equal('Settings must be an object') - done() }) }) @@ -203,21 +206,19 @@ describe('Browser: settings', function () { }) }) - it('should not change the application state if storing to the local machine results in an error', function (done) { - settings.set('foo', 'bar').then(() => { + it('should not change the application state if storing to the local machine results in an error', async function () { + await settings.set('foo', 'bar') + m.chai.expect(settings.get('foo')).to.equal('bar') + + const localSettingsWriteAllStub = m.sinon.stub(localSettings, 'writeAll') + localSettingsWriteAllStub.returns(Bluebird.reject(new Error('localSettings error'))) + + await checkError(settings.set('foo', 'baz'), (error) => { + m.chai.expect(error).to.be.an.instanceof(Error) + m.chai.expect(error.message).to.equal('localSettings error') + localSettingsWriteAllStub.restore() m.chai.expect(settings.get('foo')).to.equal('bar') - - const localSettingsWriteAllStub = m.sinon.stub(localSettings, 'writeAll') - localSettingsWriteAllStub.returns(Bluebird.reject(new Error('localSettings error'))) - - settings.set('foo', 'baz').asCallback((error) => { - m.chai.expect(error).to.be.an.instanceof(Error) - m.chai.expect(error.message).to.equal('localSettings error') - localSettingsWriteAllStub.restore() - m.chai.expect(settings.get('foo')).to.equal('bar') - done() - }) - }).catch(done) + }) }) }) diff --git a/tests/gui/modules/progress-status.spec.js b/tests/gui/modules/progress-status.spec.js index 42e3e95b..8c4fd44c 100644 --- a/tests/gui/modules/progress-status.spec.js +++ b/tests/gui/modules/progress-status.spec.js @@ -1,6 +1,7 @@ 'use strict' const m = require('mochainon') +// eslint-disable-next-line node/no-missing-require const settings = require('../../../lib/gui/app/models/settings') // eslint-disable-next-line node/no-missing-require const progressStatus = require('../../../lib/gui/app/modules/progress-status')