refactor: extract SelectionStateModel stateless functions (#982)

`SelectionStateModel`'s methods `isSystemDrive` and `isDriveLocked`
don't depend on application state. They have been extracted in a different
AngularJS service: `DriveConstraintsModel`. The new service's actual
implementation is in `lib/src`, in order to be reused by the CLI.

Miscellaneous changes:

- Rename `lib/src` to `lib/shared`
- Refactor `drive-constraints` to throw when image is undefined

The default behaviour was to pretend that we're all good if the if
the image is not specified. We're not using this "feature", and
it can be dangerous if we forget to pass in the image.

- Make `isSystemDrive` return `false` if `system` property is undefined
- Use `drive-constraints` in `store.js`

Change-Type: patch
This commit is contained in:
Ștefan Daniel Mihăilă 2017-01-06 18:42:11 +02:00 committed by Juan Cruz Viotti
parent dc32699b65
commit 050d187e6f
24 changed files with 529 additions and 414 deletions

View File

@ -108,7 +108,7 @@ be documented instead!
[lego-blocks]: https://github.com/sindresorhus/ama/issues/10#issuecomment-117766328
[etcher-image-write]: https://github.com/resin-io-modules/etcher-image-write
[exit-codes]: https://github.com/resin-io/etcher/blob/master/lib/src/exit-codes.js
[exit-codes]: https://github.com/resin-io/etcher/blob/master/lib/shared/exit-codes.js
[cli-dir]: https://github.com/resin-io/etcher/tree/master/lib/cli
[gui-dir]: https://github.com/resin-io/etcher/tree/master/lib/gui
[electron]: http://electron.atom.io

View File

@ -71,7 +71,7 @@ Exit codes
----------
The Etcher CLI uses certain exit codes to signal the result of the operation.
These are documented in [`lib/src/exit-codes.js`][exit-codes] and are also
These are documented in [`lib/shared/exit-codes.js`][exit-codes] and are also
printed on the Etcher CLI help page.
[exit-codes]: https://github.com/resin-io/etcher/blob/master/lib/src/exit-codes.js
[exit-codes]: https://github.com/resin-io/etcher/blob/master/lib/shared/exit-codes.js

View File

@ -20,7 +20,7 @@ const _ = require('lodash');
const fs = require('fs');
const yargs = require('yargs');
const utils = require('./utils');
const EXIT_CODES = require('../src/exit-codes');
const EXIT_CODES = require('../shared/exit-codes');
const packageJSON = require('../../package.json');
/**

View File

@ -25,7 +25,7 @@ const drivelist = Bluebird.promisifyAll(require('drivelist'));
const writer = require('./writer');
const utils = require('./utils');
const options = require('./cli');
const EXIT_CODES = require('../src/exit-codes');
const EXIT_CODES = require('../shared/exit-codes');
isElevated().then((elevated) => {
if (!elevated) {

View File

@ -24,7 +24,7 @@
var angular = require('angular');
const electron = require('electron');
const EXIT_CODES = require('../src/exit-codes');
const EXIT_CODES = require('../shared/exit-codes');
/* eslint-enable no-var */

View File

@ -18,7 +18,13 @@
const _ = require('lodash');
module.exports = function($q, $uibModalInstance, DrivesModel, SelectionStateModel, WarningModalService) {
module.exports = function(
$q,
$uibModalInstance,
DrivesModel,
SelectionStateModel,
WarningModalService,
DriveConstraintsModel) {
/**
* @summary The drive selector state
@ -27,6 +33,13 @@ module.exports = function($q, $uibModalInstance, DrivesModel, SelectionStateMode
*/
this.state = SelectionStateModel;
/**
* @summary Static methods to check a drive's properties
* @property
* @type Object
*/
this.constraints = DriveConstraintsModel;
/**
* @summary The drives model
* @property
@ -55,11 +68,11 @@ module.exports = function($q, $uibModalInstance, DrivesModel, SelectionStateMode
* });
*/
const shouldChangeDriveSelectionState = (drive) => {
if (!SelectionStateModel.isDriveValid(drive)) {
if (!DriveConstraintsModel.isDriveValid(drive, SelectionStateModel.getImage())) {
return $q.resolve(false);
}
if (SelectionStateModel.isDriveSizeRecommended(drive)) {
if (DriveConstraintsModel.isDriveSizeRecommended(drive, SelectionStateModel.getImage())) {
return $q.resolve(true);
}

View File

@ -27,6 +27,7 @@ const DriveSelector = angular.module(MODULE_NAME, [
require('../warning-modal/warning-modal'),
require('../../models/drives'),
require('../../models/selection-state'),
require('../../models/drive-constraints'),
require('../../utils/byte-size/byte-size')
]);

View File

@ -6,7 +6,7 @@
<div class="component-drive-selector-body modal-body">
<ul class="list-group">
<li class="list-group-item" ng-repeat="drive in modal.drives.getDrives() track by drive.device"
ng-disabled="!modal.state.isDriveValid(drive)"
ng-disabled="!modal.constraints.isDriveValid(drive, modal.state.getImage())"
ng-dblclick="modal.selectDriveAndClose(drive)"
ng-click="modal.toggleDrive(drive)">
<div>
@ -16,7 +16,7 @@
<footer class="list-group-item-footer">
<span class="label label-warning"
ng-show="modal.state.isDriveLargeEnough(drive) && !modal.state.isDriveLocked(drive) && !modal.state.isDriveSizeRecommended(drive)">
ng-show="modal.constraints.isDriveLargeEnough(drive, modal.state.getImage()) && !modal.constraints.isDriveLocked(drive) && !modal.constraints.isDriveSizeRecommended(drive, modal.state.getImage())">
<i class="glyphicon glyphicon-warning-sign"></i>
NOT RECOMMENDED</span>
@ -26,24 +26,24 @@
<!-- state is irrelevent if the drive is not large enough anyway. -->
<span class="label label-danger"
ng-hide="modal.state.isDriveLargeEnough(drive)">
ng-hide="modal.constraints.isDriveLargeEnough(drive, modal.state.getImage())">
<i class="glyphicon glyphicon-resize-small"></i>
TOO SMALL FOR IMAGE</span>
<span class="label label-danger"
ng-show="modal.state.isDriveLocked(drive) && modal.state.isDriveLargeEnough(drive)">
ng-show="modal.constraints.isDriveLocked(drive) && modal.constraints.isDriveLargeEnough(drive, modal.state.getImage())">
<i class="glyphicon glyphicon-lock"></i>
LOCKED</span>
<span class="label label-danger"
ng-show="modal.state.isSystemDrive(drive)">
ng-show="modal.constraints.isSystemDrive(drive)">
<i class="glyphicon glyphicon-hdd"></i>
SYSTEM DRIVE</span>
</footer>
</div>
<span class="tick tick--success"
ng-show="modal.state.isDriveValid(drive)"
ng-show="modal.constraints.isDriveValid(drive, modal.state.getImage())"
ng-disabled="!modal.state.isCurrentDrive(drive.device)"></span>
</li>
</ul>

View File

@ -0,0 +1,37 @@
/*
* Copyright 2016 resin.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';
/**
* @summary Expose a CLI/GUI shared utility object as an AngularJS service
* @module Etcher.Models.DriveConstraints
*/
const angular = require('angular');
const MODULE_NAME = 'Etcher.Models.DriveConstraints';
const DriveConstraintsModel = angular.module(MODULE_NAME, []);
const DriveConstraints = require('../../shared/drive-constraints');
// `DriveConstraintsModel.service` expects a constructor as the second argument, but we want
// to expose an object. Calling `DriveConstraintsModel.factory` with a a function returning
// the object is the right way to do it.
DriveConstraintsModel.factory('DriveConstraintsModel', () => {
return DriveConstraints;
});
module.exports = MODULE_NAME;

View File

@ -47,149 +47,6 @@ SelectionStateModel.service('SelectionStateModel', function(DrivesModel) {
});
};
/**
* @summary Check if a drive is large enough for the selected image
* @function
* @public
*
* @description
* For convenience, if there is no image selected, this function
* returns true.
*
* Notice that if you select the drive before the image, the check
* will not take place and it'll be the client's responsibility
* to do so.
*
* @param {Object} drive - drive
* @returns {Boolean} whether the drive is large enough
*
* @example
* SelectionStateModel.setImage({
* path: 'rpi.img',
* size: 100000000
* });
*
* if (SelectionStateModel.isDriveLargeEnough({
* device: '/dev/disk2',
* name: 'My Drive',
* size: 123456789
* })) {
* console.log('We can flash the image to this drive!');
* }
*/
this.isDriveLargeEnough = (drive) => {
return (this.getImageSize() || 0) <= drive.size;
};
/**
* @summary Check if a drive meets the recommended drive size suggestion
* @function
* @public
*
* @description
* For convenience, if there is no image selected, this function
* returns true.
*
* @param {Object} drive - drive
* @returns {Boolean} whether the drive size is recommended
*
* @example
* SelectionStateModel.setImage({
* path: 'rpi.img',
* size: 100000000
* recommendedDriveSize: 200000000
* });
*
* if (SelectionStateModel.isDriveSizeRecommended({
* device: '/dev/disk2',
* name: 'My Drive',
* size: 400000000
* })) {
* console.log('We meet the recommended drive size!');
* }
*/
this.isDriveSizeRecommended = (drive) => {
return drive.size >= (this.getImageRecommendedDriveSize() || 0);
};
/**
* @summary Check if a drive is locked
* @function
* @public
*
* @description
* This usually points out a locked SD Card.
*
* @param {Object} drive - drive
* @returns {Boolean} whether the drive is locked
*
* @example
* if (SelectionStateModel.isDriveLocked({
* device: '/dev/disk2',
* name: 'My Drive',
* size: 123456789,
* protected: true
* })) {
* console.log('This drive is locked (e.g: write-protected)');
* }
*/
this.isDriveLocked = (drive) => {
return _.get(drive, 'protected', false);
};
/**
* @summary Check if a drive is valid
* @function
* @public
*
* @description
* This function is a facade to:
*
* - `SelectionStateModel.isDriveLargeEnough()`
* - `SelectionStateModel.isDriveLocked()`
*
* @param {Object} drive - drive
* @returns {Boolean} whether the drive is valid
*
* @example
* if (SelectionStateModel.isDriveValid({
* device: '/dev/disk2',
* name: 'My Drive',
* size: 123456789,
* protected: true
* })) {
* console.log('This drive is valid!');
* }
*/
this.isDriveValid = (drive) => {
return _.every([
this.isDriveLargeEnough(drive),
!this.isDriveLocked(drive)
]);
};
/**
* @summary Check if a drive is a system drive
* @function
* @public
* @param {Object} drive - drive
* @returns {Boolean} whether the drive is a system drive
*
* @example
* if (SelectionStateModel.isSystemDrive({
* device: '/dev/disk2',
* name: 'My Drive',
* size: 123456789,
* protected: true,
* system: true
* })) {
* console.log('This drive is a system drive!');
* }
*/
this.isSystemDrive = (drive) => {
return Boolean(drive.system);
};
/**
* @summary Toggle set drive
* @function
@ -243,6 +100,20 @@ SelectionStateModel.service('SelectionStateModel', function(DrivesModel) {
});
};
/**
* @summary Get the selected image
* @function
* @public
*
* @returns {object}
*
* @example
* const image = SelectionStateModel.getImage();
*/
this.getImage = () => {
return _.get(Store.getState().toJS(), 'selection.image');
};
/**
* @summary Get image path
* @function
@ -370,7 +241,7 @@ SelectionStateModel.service('SelectionStateModel', function(DrivesModel) {
* }
*/
this.hasImage = () => {
return Boolean(this.getImagePath() && this.getImageSize());
return Boolean(this.getImage());
};
/**

View File

@ -20,6 +20,7 @@ const Immutable = require('immutable');
const _ = require('lodash');
const redux = require('redux');
const persistState = require('redux-localstorage');
const constraints = require('../../shared/drive-constraints');
/**
* @summary Application default state
@ -94,15 +95,13 @@ const storeReducer = (state, action) => {
const drive = _.first(action.data);
// Even if there's no image selectected, we need to call `isDriveValid`
// to check if the drive is locked, and `{}` works fine with it
const image = state.getIn([ 'selection', 'image' ], Immutable.fromJS({})).toJS();
if (_.every([
// TODO: Reuse from SelectionStateModel.isDriveValid()
state.getIn([ 'selection', 'image', 'size' ], 0) <= drive.size,
// TODO: Reuse from SelectionStateModel.isDriveSizeRecommended()
state.getIn([ 'selection', 'image', 'recommendedDriveSize' ], 0) <= drive.size,
!drive.protected
constraints.isDriveValid(drive, image),
constraints.isDriveSizeRecommended(drive, image)
])) {
return storeReducer(newState, {
type: ACTIONS.SELECT_DRIVE,
@ -225,8 +224,8 @@ const storeReducer = (state, action) => {
throw new Error('The drive is write-protected');
}
// TODO: Reuse from SelectionStateModel.isDriveLargeEnough()
if (state.getIn([ 'selection', 'image', 'size' ], 0) > selectedDrive.get('size')) {
const image = state.getIn([ 'selection', 'image' ]);
if (image && !constraints.isDriveLargeEnough(selectedDrive.toJS(), image.toJS())) {
throw new Error('The drive is not large enough');
}
@ -268,9 +267,9 @@ const storeReducer = (state, action) => {
});
return _.attempt(() => {
if (_.some([
selectedDrive && selectedDrive.get('size', 0) < action.data.size,
selectedDrive && selectedDrive.get('size', 0) < action.data.recommendedDriveSize
if (selectedDrive && !_.every([
constraints.isDriveLargeEnough(selectedDrive.toJS(), action.data),
constraints.isDriveSizeRecommended(selectedDrive.toJS(), action.data)
])) {
return storeReducer(state, {
type: ACTIONS.REMOVE_DRIVE

View File

@ -21,7 +21,7 @@
*/
const angular = require('angular');
const childWriter = require('../../src/child-writer');
const childWriter = require('../../shared/child-writer');
const MODULE_NAME = 'Etcher.Modules.ImageWriter';
const imageWriter = angular.module(MODULE_NAME, [

View File

@ -0,0 +1,149 @@
/*
* Copyright 2016 resin.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';
/**
* @summary Check if a drive is locked
* @function
* @public
*
* @description
* This usually points out a locked SD Card.
*
* @param {Object} drive - drive
* @returns {Boolean} whether the drive is locked
*
* @example
* if (constraints.isDriveLocked({
* device: '/dev/disk2',
* name: 'My Drive',
* size: 123456789,
* protected: true
* })) {
* console.log('This drive is locked (e.g: write-protected)');
* }
*/
exports.isDriveLocked = (drive) => {
return Boolean(drive.protected || false);
};
/**
* @summary Check if a drive is a system drive
* @function
* @public
* @param {Object} drive - drive
* @returns {Boolean} whether the drive is a system drive
*
* @example
* if (constraints.isSystemDrive({
* device: '/dev/disk2',
* name: 'My Drive',
* size: 123456789,
* protected: true,
* system: true
* })) {
* console.log('This drive is a system drive!');
* }
*/
exports.isSystemDrive = (drive) => {
return Boolean(drive.system || false);
};
/**
* @summary Check if a drive is large enough for an image
* @function
* @public
*
* @param {Object} drive - drive
* @param {Object} image - image
* @returns {Boolean} whether the drive is large enough
*
* @example
* if (constraints.isDriveLargeEnough({
* device: '/dev/disk2',
* name: 'My Drive',
* size: 1000000000
* }, {
* path: 'rpi.img',
* size: 1000000000
* })) {
* console.log('We can flash the image to this drive!');
* }
*/
exports.isDriveLargeEnough = (drive, image) => {
return drive.size >= (image.size || 0);
};
/**
* @summary Check if a drive is is valid, i.e. not locked and large enough for an image
* @function
* @public
*
* @param {Object} drive - drive
* @param {Object} image - image
* @returns {Boolean} whether the drive is valid
*
* @example
* if (constraints.isDriveValid({
* device: '/dev/disk2',
* name: 'My Drive',
* size: 1000000000,
* protected: false
* }, {
* path: 'rpi.img',
* size: 1000000000,
* recommendedDriveSize: 2000000000
* })) {
* console.log('This drive is valid!');
* }
*/
exports.isDriveValid = (drive, image) => {
return !this.isDriveLocked(drive) && this.isDriveLargeEnough(drive, image);
};
/**
* @summary Check if a drive meets the recommended drive size suggestion
* @function
* @public
*
* @description
* If the image doesn't have a recommended size, this function returns true.
*
* @param {Object} drive - drive
* @param {Object} image - image
* @returns {Boolean} whether the drive size is recommended
*
* @example
* const drive = {
* device: '/dev/disk2',
* name: 'My Drive',
* size: 4000000000
* };
*
* const image = {
* path: 'rpi.img',
* size: 2000000000
* recommendedDriveSize: 4000000000
* });
*
* if (constraints.isDriveSizeRecommended(drive, image)) {
* console.log('We meet the recommended drive size!');
* }
*/
exports.isDriveSizeRecommended = (drive, image) => {
return drive.size >= (image.recommendedDriveSize || 0);
};

View File

@ -11,7 +11,7 @@
"url": "git@github.com:resin-io/etcher.git"
},
"scripts": {
"test": "npm run lint && electron-mocha --recursive --renderer tests/gui -R min && electron-mocha --recursive tests/cli -R min",
"test": "npm run lint && electron-mocha --recursive --renderer tests/gui -R min && electron-mocha --recursive tests/cli tests/shared -R min",
"sass": "node-sass ./lib/gui/scss/main.scss > build/css/main.css",
"jslint": "eslint lib tests scripts bin versionist.conf.js",
"scsslint": "scss-lint lib/gui/scss",

View File

@ -0,0 +1,28 @@
'use strict';
const m = require('mochainon');
const angular = require('angular');
require('angular-mocks');
describe('Browser: DriveConstraints', function() {
beforeEach(angular.mock.module(
require('../../../lib/gui/models/drive-constraints')
));
describe('DriveConstraintsModel', function() {
let DriveConstraintsModel;
beforeEach(angular.mock.inject(function(_DriveConstraintsModel_) {
DriveConstraintsModel = _DriveConstraintsModel_;
}));
it('should be the `lib/shared/drive-constraints.js` object', function() {
const DriveConstraints = require('../../../lib/shared/drive-constraints');
m.chai.expect(Object.is(DriveConstraintsModel, DriveConstraints)).to.be.true;
});
});
});

View File

@ -35,6 +35,10 @@ describe('Browser: SelectionState', function() {
m.chai.expect(drive).to.be.undefined;
});
it('getImage() should return undefined', function() {
m.chai.expect(SelectionStateModel.getImage()).to.be.undefined;
});
it('getImagePath() should return undefined', function() {
m.chai.expect(SelectionStateModel.getImagePath()).to.be.undefined;
});
@ -75,95 +79,6 @@ describe('Browser: SelectionState', function() {
});
describe('.isDriveLocked()', function() {
it('should return true if the drive is protected', function() {
const result = SelectionStateModel.isDriveLocked({
device: '/dev/disk2',
name: 'USB Drive',
size: 999999999,
protected: true
});
m.chai.expect(result).to.be.true;
});
it('should return false if the drive is not protected', function() {
const result = SelectionStateModel.isDriveLocked({
device: '/dev/disk2',
name: 'USB Drive',
size: 999999999,
protected: false
});
m.chai.expect(result).to.be.false;
});
it('should return false if we don\'t know if the drive is protected', function() {
const result = SelectionStateModel.isDriveLocked({
device: '/dev/disk2',
name: 'USB Drive',
size: 999999999
});
m.chai.expect(result).to.be.false;
});
});
describe('.isDriveValid()', function() {
it('should return true if the drive is not locked', function() {
const result = SelectionStateModel.isDriveValid({
device: '/dev/disk2',
name: 'USB Drive',
size: 999999999,
protected: false
});
m.chai.expect(result).to.be.true;
});
it('should return false if the drive is locked', function() {
const result = SelectionStateModel.isDriveValid({
device: '/dev/disk2',
name: 'USB Drive',
size: 999999999,
protected: true
});
m.chai.expect(result).to.be.false;
});
});
describe('.isSystemDrive()', function() {
it('should return true if the drive is a system drive', function() {
const result = SelectionStateModel.isSystemDrive({
device: '/dev/disk2',
name: 'USB Drive',
size: 999999999,
protected: true,
system: true
});
m.chai.expect(result).to.be.true;
});
it('should return false if the drive is a removable drive', function() {
const result = SelectionStateModel.isSystemDrive({
device: '/dev/disk2',
name: 'USB Drive',
size: 999999999,
protected: true,
system: false
});
m.chai.expect(result).to.be.false;
});
});
describe('given a drive', function() {
beforeEach(function() {
@ -302,7 +217,7 @@ describe('Browser: SelectionState', function() {
describe('given an image', function() {
beforeEach(function() {
SelectionStateModel.setImage({
this.image = {
path: 'foo.img',
size: 999999999,
recommendedDriveSize: 1000000000,
@ -310,129 +225,9 @@ describe('Browser: SelectionState', function() {
supportUrl: 'https://www.raspbian.org/forums/',
name: 'Raspbian',
logo: '<svg><text fill="red">Raspbian</text></svg>'
});
});
describe('.isDriveLargeEnough()', function() {
it('should return true if the drive size is greater than the image size', function() {
const result = SelectionStateModel.isDriveLargeEnough({
device: '/dev/disk1',
name: 'USB Drive',
size: 99999999999999,
protected: false
});
m.chai.expect(result).to.be.true;
});
it('should return true if the drive size is equal to the image size', function() {
const result = SelectionStateModel.isDriveLargeEnough({
device: '/dev/disk1',
name: 'USB Drive',
size: 999999999,
protected: false
});
m.chai.expect(result).to.be.true;
});
it('should return false if the drive size is less than the image size', function() {
const result = SelectionStateModel.isDriveLargeEnough({
device: '/dev/disk1',
name: 'USB Drive',
size: 999999998,
protected: false
});
m.chai.expect(result).to.be.false;
});
});
describe('.isDriveSizeRecommended()', function() {
it('should return true if the drive size is greater than the recommended size', function() {
const result = SelectionStateModel.isDriveSizeRecommended({
device: '/dev/disk1',
name: 'USB Drive',
size: 1000000001,
protected: false
});
m.chai.expect(result).to.be.true;
});
it('should return true if the drive size is equal to the recommended size', function() {
const result = SelectionStateModel.isDriveSizeRecommended({
device: '/dev/disk1',
name: 'USB Drive',
size: 1000000000,
protected: false
});
m.chai.expect(result).to.be.true;
});
it('should return false if the drive size is less than the recommended size', function() {
const result = SelectionStateModel.isDriveSizeRecommended({
device: '/dev/disk1',
name: 'USB Drive',
size: 999999999,
protected: false
});
m.chai.expect(result).to.be.false;
});
});
describe('.isDriveValid()', function() {
it('should return true if the drive is large enough and it is not locked', function() {
const result = SelectionStateModel.isDriveValid({
device: '/dev/disk1',
name: 'USB Drive',
size: 99999999999999,
protected: false
});
m.chai.expect(result).to.be.true;
});
it('should return false if the drive is large enough but it is locked', function() {
const result = SelectionStateModel.isDriveValid({
device: '/dev/disk1',
name: 'USB Drive',
size: 99999999999999,
protected: true
});
m.chai.expect(result).to.be.false;
});
it('should return false if the drive is not large enough and it is not locked', function() {
const result = SelectionStateModel.isDriveValid({
device: '/dev/disk1',
name: 'USB Drive',
size: 1,
protected: false
});
m.chai.expect(result).to.be.false;
});
it('should return false if the drive is not large enough and it is locked', function() {
const result = SelectionStateModel.isDriveValid({
device: '/dev/disk1',
name: 'USB Drive',
size: 1,
protected: true
});
m.chai.expect(result).to.be.false;
});
};
SelectionStateModel.setImage(this.image);
});
describe('.setDrive()', function() {
@ -454,6 +249,14 @@ describe('Browser: SelectionState', function() {
});
describe('.getImage()', function() {
it('should return the image', function() {
m.chai.expect(SelectionStateModel.getImage()).to.deep.equal(this.image);
});
});
describe('.getImagePath()', function() {
it('should return the image path', function() {
@ -559,34 +362,6 @@ describe('Browser: SelectionState', function() {
describe('given no image', function() {
describe('.isDriveLargeEnough()', function() {
it('should return true', function() {
const result = SelectionStateModel.isDriveLargeEnough({
device: '/dev/disk1',
name: 'USB Drive',
size: 1
});
m.chai.expect(result).to.be.true;
});
});
describe('.isDriveSizeRecommended()', function() {
it('should return true', function() {
const result = SelectionStateModel.isDriveSizeRecommended({
device: '/dev/disk1',
name: 'USB Drive',
size: 1
});
m.chai.expect(result).to.be.true;
});
});
describe('.setImage()', function() {
it('should be able to set an image', function() {

View File

@ -0,0 +1,242 @@
'use strict';
const m = require('mochainon');
const constraints = require('../../lib/shared/drive-constraints');
describe('Shared: DriveConstraints', function() {
describe('.isDriveLocked()', function() {
it('should return true if the drive is protected', function() {
const result = constraints.isDriveLocked({
device: '/dev/disk2',
name: 'USB Drive',
size: 999999999,
protected: true
});
m.chai.expect(result).to.be.true;
});
it('should return false if the drive is not protected', function() {
const result = constraints.isDriveLocked({
device: '/dev/disk2',
name: 'USB Drive',
size: 999999999,
protected: false
});
m.chai.expect(result).to.be.false;
});
it('should return false if we don\'t know if the drive is protected', function() {
const result = constraints.isDriveLocked({
device: '/dev/disk2',
name: 'USB Drive',
size: 999999999
});
m.chai.expect(result).to.be.false;
});
});
describe('.isSystemDrive()', function() {
it('should return true if the drive is a system drive', function() {
const result = constraints.isSystemDrive({
device: '/dev/disk2',
name: 'USB Drive',
size: 999999999,
protected: true,
system: true
});
m.chai.expect(result).to.be.true;
});
it('should default to `false` if the `system` property is `undefined`', function() {
const result = constraints.isSystemDrive({
device: '/dev/disk2',
name: 'USB Drive',
size: 999999999,
protected: true
});
m.chai.expect(result).to.be.false;
});
it('should return false if the drive is a removable drive', function() {
const result = constraints.isSystemDrive({
device: '/dev/disk2',
name: 'USB Drive',
size: 999999999,
protected: true,
system: false
});
m.chai.expect(result).to.be.false;
});
});
describe('.isDriveLargeEnough()', function() {
it('should return true if the drive size is greater than the image size', function() {
const result = constraints.isDriveLargeEnough({
device: '/dev/disk1',
name: 'USB Drive',
size: 1000000001,
protected: false
}, {
path: 'rpi.img',
size: 1000000000
});
m.chai.expect(result).to.be.true;
});
it('should return true if the drive size is equal to the image size', function() {
const result = constraints.isDriveLargeEnough({
device: '/dev/disk1',
name: 'USB Drive',
size: 1000000000,
protected: false
}, {
path: 'rpi.img',
size: 1000000000
});
m.chai.expect(result).to.be.true;
});
it('should return false if the drive size is less than the image size', function() {
const result = constraints.isDriveLargeEnough({
device: '/dev/disk1',
name: 'USB Drive',
size: 1000000000,
protected: false
}, {
path: 'rpi.img',
size: 1000000001
});
m.chai.expect(result).to.be.false;
});
});
describe('.isDriveSizeRecommended()', function() {
it('should return true if the drive size is greater than the recommended size ', function() {
const result = constraints.isDriveSizeRecommended({
device: '/dev/disk1',
name: 'USB Drive',
size: 2000000001,
protected: false
}, {
path: 'rpi.img',
size: 1000000000,
recommendedDriveSize: 2000000000
});
m.chai.expect(result).to.be.true;
});
it('should return true if the drive size is equal to recommended size', function() {
const result = constraints.isDriveSizeRecommended({
device: '/dev/disk1',
name: 'USB Drive',
size: 2000000000,
protected: false
}, {
path: 'rpi.img',
size: 1000000000,
recommendedDriveSize: 2000000000
});
m.chai.expect(result).to.be.true;
});
it('should return false if the drive size is less than the recommended size', function() {
const result = constraints.isDriveSizeRecommended({
device: '/dev/disk1',
name: 'USB Drive',
size: 2000000000,
protected: false
}, {
path: 'rpi.img',
size: 1000000000,
recommendedDriveSize: 2000000001
});
m.chai.expect(result).to.be.false;
});
it('should return true if the recommended drive size is undefined', function() {
const result = constraints.isDriveSizeRecommended({
device: '/dev/disk1',
name: 'USB Drive',
size: 2000000000,
protected: false
}, {
path: 'rpi.img',
size: 1000000000
});
m.chai.expect(result).to.be.true;
});
});
describe('.isDriveValid()', function() {
describe('given drive is large enough', function() {
beforeEach(function() {
this.drive = {
device: '/dev/disk2',
name: 'My Drive',
size: 4000000000
};
this.image = {
path: 'rpi.img',
size: 2000000000
};
});
it('should return true if drive is not locked', function() {
this.drive.protected = false;
m.chai.expect(constraints.isDriveValid(this.drive, this.image)).to.be.true;
});
it('should return false if drive is locked', function() {
this.drive.protected = true;
m.chai.expect(constraints.isDriveValid(this.drive, this.image)).to.be.false;
});
});
describe('given drive is not large enough', function() {
beforeEach(function() {
this.drive = {
device: '/dev/disk2',
name: 'My Drive',
size: 1000000000
};
this.image = {
path: 'rpi.img',
size: 2000000000
};
});
it('should return false', function() {
m.chai.expect(constraints.isDriveValid(this.drive, this.image)).to.be.false;
});
});
});
});