mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-22 10:46:31 +00:00
Prevent selection of drives that are not large enough (#408)
* Make .selectImage() dialog return an object with a `path` property This allows to return more than one value for the selected image, like image size and other metadata for example. Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com> * Return image size from .selectImage() dialog This property will be useful to perform some sanity checks, like ensuring the selected drive is large enough to contain the image. Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com> * Store both the image path and the image size in the selection model In order to simplify accessing such properties in an encapsulated manner, `SelectionStateModel.getImage()` was replaced with the following functions: - `SelectionStateModel.getImagePath()`. - `SelectionStateModel.getImageSize()`. Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com> * Increase SelectionStateModel setter validation The model not providing any kind of validation was the source of some bugs I encountered while implementing the previous commits that would not have happened otherwise. Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com> * Prevent selection of drives that are not large enough - The drive selector modal was modified such that drives that are not large enough are crossed out, and the user is not able to click them. - In the case of drive auto-selection, not large enough drives won't attempt to get autoselected. This commit introduces: - The `SelectionStateModel.isDriveLargeEnough()` function. Fixes: https://github.com/resin-io/etcher/issues/344 Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
This commit is contained in:
parent
c31ccdbdbe
commit
bf37ee72df
@ -6252,6 +6252,25 @@ button.btn:focus, button.progress-button:focus {
|
|||||||
.alert-ribbon--open {
|
.alert-ribbon--open {
|
||||||
top: 0; }
|
top: 0; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
.list-group-item[disabled] {
|
||||||
|
text-decoration: line-through;
|
||||||
|
cursor: not-allowed; }
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2016 Resin.io
|
* Copyright 2016 Resin.io
|
||||||
*
|
*
|
||||||
|
@ -160,10 +160,14 @@ app.controller('AppController', function(
|
|||||||
// `angular.equals` is used instead of `_.isEqual` to
|
// `angular.equals` is used instead of `_.isEqual` to
|
||||||
// cope with `$$hashKey`.
|
// cope with `$$hashKey`.
|
||||||
if (!angular.equals(self.selection.getDrive(), drive)) {
|
if (!angular.equals(self.selection.getDrive(), drive)) {
|
||||||
AnalyticsService.logEvent('Auto-select drive', {
|
|
||||||
device: drive.device
|
if (self.selection.isDriveLargeEnough(drive)) {
|
||||||
});
|
self.selectDrive(drive);
|
||||||
self.selectDrive(drive);
|
|
||||||
|
AnalyticsService.logEvent('Auto-select drive', {
|
||||||
|
device: drive.device
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -181,9 +185,7 @@ app.controller('AppController', function(
|
|||||||
|
|
||||||
this.selectImage = function(image) {
|
this.selectImage = function(image) {
|
||||||
self.selection.setImage(image);
|
self.selection.setImage(image);
|
||||||
AnalyticsService.logEvent('Select image', {
|
AnalyticsService.logEvent('Select image', image);
|
||||||
image: image
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.openImageSelector = function() {
|
this.openImageSelector = function() {
|
||||||
|
@ -6,12 +6,15 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<li class="list-group-item" ng-repeat="drive in modal.scanner.drives"
|
<li class="list-group-item" ng-repeat="drive in modal.scanner.drives"
|
||||||
ng-click="modal.state.toggleSetDrive(drive)">
|
ng-disabled="!modal.state.isDriveLargeEnough(drive)"
|
||||||
|
ng-click="modal.state.isDriveLargeEnough(drive) && modal.state.toggleSetDrive(drive)">
|
||||||
<div>
|
<div>
|
||||||
<h4 class="list-group-item-heading">{{ drive.description }} - {{ drive.size | gigabyte | number:1 }} GB</h4>
|
<h4 class="list-group-item-heading">{{ drive.description }} - {{ drive.size | gigabyte | number:1 }} GB</h4>
|
||||||
<p class="list-group-item-text">{{ drive.name }}</p>
|
<p class="list-group-item-text">{{ drive.name }}</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="tick tick--success" ng-disabled="!modal.state.isCurrentDrive(drive)"></span>
|
<span class="tick tick--success"
|
||||||
|
ng-show="modal.state.isDriveLargeEnough(drive)"
|
||||||
|
ng-disabled="!modal.state.isCurrentDrive(drive)"></span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,15 +42,88 @@ SelectionStateModel.service('SelectionStateModel', function() {
|
|||||||
*
|
*
|
||||||
* @param {Object} drive - drive
|
* @param {Object} drive - drive
|
||||||
*
|
*
|
||||||
|
* @throws Will throw if drive lacks `.device`.
|
||||||
|
* @throws Will throw if `drive.device` is not a string.
|
||||||
|
* @throws Will throw if drive lacks `.name`.
|
||||||
|
* @throws Will throw if `drive.name` is not a string.
|
||||||
|
* @throws Will throw if drive lacks `.size`.
|
||||||
|
* @throws Will throw if `drive.size` is not a number.
|
||||||
|
* @throws Will throw if there is an image and the drive is not large enough.
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* SelectionStateModel.setDrive({
|
* SelectionStateModel.setDrive({
|
||||||
* device: '/dev/disk2'
|
* device: '/dev/disk2',
|
||||||
|
* name: 'USB drive',
|
||||||
|
* size: 999999999
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
this.setDrive = function(drive) {
|
this.setDrive = function(drive) {
|
||||||
|
|
||||||
|
if (!drive.device) {
|
||||||
|
throw new Error('Missing drive device');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_.isString(drive.device)) {
|
||||||
|
throw new Error(`Invalid drive device: ${drive.device}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!drive.name) {
|
||||||
|
throw new Error('Missing drive name');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_.isString(drive.name)) {
|
||||||
|
throw new Error(`Invalid drive name: ${drive.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!drive.size) {
|
||||||
|
throw new Error('Missing drive size');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_.isNumber(drive.size)) {
|
||||||
|
throw new Error(`Invalid drive size: ${drive.size}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self.isDriveLargeEnough(drive)) {
|
||||||
|
throw new Error('The drive is not large enough');
|
||||||
|
}
|
||||||
|
|
||||||
selection.drive = drive;
|
selection.drive = drive;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 = function(drive) {
|
||||||
|
return (self.getImageSize() || 0) <= drive.size;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Toggle set drive
|
* @summary Toggle set drive
|
||||||
* @function
|
* @function
|
||||||
@ -76,12 +149,36 @@ SelectionStateModel.service('SelectionStateModel', function() {
|
|||||||
* @function
|
* @function
|
||||||
* @public
|
* @public
|
||||||
*
|
*
|
||||||
* @param {String} image - image
|
* @param {Object} image - image
|
||||||
|
*
|
||||||
|
* @throws Will throw if image lacks `.path`.
|
||||||
|
* @throws Will throw if `image.path` is not a string.
|
||||||
|
* @throws Will throw if image lacks `.size`.
|
||||||
|
* @throws Will throw if `image.size` is not a number.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* SelectionStateModel.setImage('foo.img');
|
* SelectionStateModel.setImage({
|
||||||
|
* path: 'foo.img'
|
||||||
|
* });
|
||||||
*/
|
*/
|
||||||
this.setImage = function(image) {
|
this.setImage = function(image) {
|
||||||
|
|
||||||
|
if (!image.path) {
|
||||||
|
throw new Error('Missing image path');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_.isString(image.path)) {
|
||||||
|
throw new Error(`Invalid image path: ${image.path}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!image.size) {
|
||||||
|
throw new Error('Missing image size');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_.isNumber(image.size)) {
|
||||||
|
throw new Error(`Invalid image size: ${image.size}`);
|
||||||
|
}
|
||||||
|
|
||||||
selection.image = image;
|
selection.image = image;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -104,17 +201,31 @@ SelectionStateModel.service('SelectionStateModel', function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Get image
|
* @summary Get image path
|
||||||
* @function
|
* @function
|
||||||
* @public
|
* @public
|
||||||
*
|
*
|
||||||
* @returns {String} image
|
* @returns {String} image path
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const image = SelectionStateModel.getImage();
|
* const imagePath = SelectionStateModel.getImagePath();
|
||||||
*/
|
*/
|
||||||
this.getImage = function() {
|
this.getImagePath = function() {
|
||||||
return selection.image;
|
return _.get(selection.image, 'path');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Get image size
|
||||||
|
* @function
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* @returns {Number} image size
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const imageSize = SelectionStateModel.getImageSize();
|
||||||
|
*/
|
||||||
|
this.getImageSize = function() {
|
||||||
|
return _.get(selection.image, 'size');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -146,7 +257,7 @@ SelectionStateModel.service('SelectionStateModel', function() {
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
this.hasImage = function() {
|
this.hasImage = function() {
|
||||||
return Boolean(self.getImage());
|
return Boolean(self.getImagePath() && self.getImageSize());
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -157,7 +268,7 @@ SelectionStateModel.service('SelectionStateModel', function() {
|
|||||||
* @example
|
* @example
|
||||||
* SelectionStateModel.removeDrive();
|
* SelectionStateModel.removeDrive();
|
||||||
*/
|
*/
|
||||||
this.removeDrive = _.partial(self.setDrive, undefined);
|
this.removeDrive = _.partial(_.unset, selection, 'drive');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Remove image
|
* @summary Remove image
|
||||||
@ -167,7 +278,7 @@ SelectionStateModel.service('SelectionStateModel', function() {
|
|||||||
* @example
|
* @example
|
||||||
* SelectionStateModel.removeImage();
|
* SelectionStateModel.removeImage();
|
||||||
*/
|
*/
|
||||||
this.removeImage = _.partial(self.setImage, undefined);
|
this.removeImage = _.partial(_.unset, selection, 'image');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Clear selections
|
* @summary Clear selections
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
const fs = require('fs');
|
||||||
const electron = require('electron');
|
const electron = require('electron');
|
||||||
const imageStream = require('etcher-image-stream');
|
const imageStream = require('etcher-image-stream');
|
||||||
|
|
||||||
@ -30,16 +31,16 @@ module.exports = function($q) {
|
|||||||
* @description
|
* @description
|
||||||
* Notice that by image, we mean *.img/*.iso/*.zip/etc files.
|
* Notice that by image, we mean *.img/*.iso/*.zip/etc files.
|
||||||
*
|
*
|
||||||
* @fulfil {String} - selected image
|
* @fulfil {Object} - selected image
|
||||||
* @returns {Promise};
|
* @returns {Promise};
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* OSDialogService.selectImage().then(function(image) {
|
* OSDialogService.selectImage().then(function(image) {
|
||||||
* console.log('The selected image is', image);
|
* console.log('The selected image is', image.path);
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
this.selectImage = function() {
|
this.selectImage = function() {
|
||||||
return $q(function(resolve) {
|
return $q(function(resolve, reject) {
|
||||||
electron.remote.dialog.showOpenDialog({
|
electron.remote.dialog.showOpenDialog({
|
||||||
properties: [
|
properties: [
|
||||||
'openFile'
|
'openFile'
|
||||||
@ -55,8 +56,23 @@ module.exports = function($q) {
|
|||||||
// `_.first` is smart enough to not throw and return `undefined`
|
// `_.first` is smart enough to not throw and return `undefined`
|
||||||
// if we pass it an `undefined` value (e.g: when the selection
|
// if we pass it an `undefined` value (e.g: when the selection
|
||||||
// dialog was cancelled).
|
// dialog was cancelled).
|
||||||
return resolve(_.first(files));
|
const imagePath = _.first(files);
|
||||||
|
|
||||||
|
if (!imagePath) {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.stat(imagePath, function(error, stats) {
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve({
|
||||||
|
path: imagePath,
|
||||||
|
size: stats.size
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<p class="step-footer">.img, .iso, or <span class="step-footer-underline" uib-tooltip=".zip, .xz">compressed images</span></p>
|
<p class="step-footer">.img, .iso, or <span class="step-footer-underline" uib-tooltip=".zip, .xz">compressed images</span></p>
|
||||||
</div>
|
</div>
|
||||||
<div ng-show="app.selection.hasImage()">
|
<div ng-show="app.selection.hasImage()">
|
||||||
<div ng-bind="app.selection.getImage() | basename"></div>
|
<div ng-bind="app.selection.getImagePath() | basename"></div>
|
||||||
|
|
||||||
<button class="btn btn-link step-footer"
|
<button class="btn btn-link step-footer"
|
||||||
ng-click="app.reselectImage()"
|
ng-click="app.reselectImage()"
|
||||||
@ -74,7 +74,7 @@
|
|||||||
striped="{{ app.writer.state.type == 'check' }}"
|
striped="{{ app.writer.state.type == 'check' }}"
|
||||||
ng-attr-active="{{ app.writer.isFlashing() }}"
|
ng-attr-active="{{ app.writer.isFlashing() }}"
|
||||||
ng-show="app.success"
|
ng-show="app.success"
|
||||||
ng-click="app.flash(app.selection.getImage(), app.selection.getDrive())"
|
ng-click="app.flash(app.selection.getImagePath(), app.selection.getDrive())"
|
||||||
ng-disabled="!app.selection.hasImage() || !app.selection.hasDrive()">
|
ng-disabled="!app.selection.hasImage() || !app.selection.hasDrive()">
|
||||||
<span ng-show="app.writer.state.progress == 100 && app.writer.isFlashing()">Finishing...</span>
|
<span ng-show="app.writer.state.progress == 100 && app.writer.isFlashing()">Finishing...</span>
|
||||||
<span ng-show="app.writer.state.progress == 0 && !app.writer.isFlashing()">Flash!</span>
|
<span ng-show="app.writer.state.progress == 0 && !app.writer.isFlashing()">Flash!</span>
|
||||||
|
20
lib/gui/scss/components/_list-group.scss
Normal file
20
lib/gui/scss/components/_list-group.scss
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.list-group-item[disabled] {
|
||||||
|
text-decoration: line-through;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
@ -43,6 +43,7 @@ $alert-padding: 13px;
|
|||||||
@import "./components/tick";
|
@import "./components/tick";
|
||||||
@import "./components/modal";
|
@import "./components/modal";
|
||||||
@import "./components/alert-ribbon";
|
@import "./components/alert-ribbon";
|
||||||
|
@import "./components/list-group";
|
||||||
@import "../components/update-notifier/styles/update-notifier";
|
@import "../components/update-notifier/styles/update-notifier";
|
||||||
@import "../components/progress-button/styles/progress-button";
|
@import "../components/progress-button/styles/progress-button";
|
||||||
@import "../components/svg-icon/styles/svg-icon";
|
@import "../components/svg-icon/styles/svg-icon";
|
||||||
|
@ -29,9 +29,12 @@ describe('Browser: SelectionState', function() {
|
|||||||
m.chai.expect(drive).to.be.undefined;
|
m.chai.expect(drive).to.be.undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getImage() should return undefined', function() {
|
it('getImagePath() should return undefined', function() {
|
||||||
const image = SelectionStateModel.getImage();
|
m.chai.expect(SelectionStateModel.getImagePath()).to.be.undefined;
|
||||||
m.chai.expect(image).to.be.undefined;
|
});
|
||||||
|
|
||||||
|
it('getImageSize() should return undefined', function() {
|
||||||
|
m.chai.expect(SelectionStateModel.getImageSize()).to.be.undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hasDrive() should return false', function() {
|
it('hasDrive() should return false', function() {
|
||||||
@ -46,34 +49,25 @@ describe('Browser: SelectionState', function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('given an empty object drive', function() {
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
SelectionStateModel.setDrive({});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('.getDrive()', function() {
|
|
||||||
|
|
||||||
it('should return undefined', function() {
|
|
||||||
const drive = SelectionStateModel.getDrive();
|
|
||||||
m.chai.expect(drive).to.be.undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('given a drive', function() {
|
describe('given a drive', function() {
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
SelectionStateModel.setDrive('/dev/disk2');
|
SelectionStateModel.setDrive({
|
||||||
|
device: '/dev/disk2',
|
||||||
|
name: 'USB Drive',
|
||||||
|
size: 999999999
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('.getDrive()', function() {
|
describe('.getDrive()', function() {
|
||||||
|
|
||||||
it('should return the drive', function() {
|
it('should return the drive', function() {
|
||||||
const drive = SelectionStateModel.getDrive();
|
const drive = SelectionStateModel.getDrive();
|
||||||
m.chai.expect(drive).to.equal('/dev/disk2');
|
m.chai.expect(drive).to.deep.equal({
|
||||||
|
device: '/dev/disk2',
|
||||||
|
name: 'USB Drive',
|
||||||
|
size: 999999999
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -90,9 +84,18 @@ describe('Browser: SelectionState', function() {
|
|||||||
describe('.setDrive()', function() {
|
describe('.setDrive()', function() {
|
||||||
|
|
||||||
it('should override the drive', function() {
|
it('should override the drive', function() {
|
||||||
SelectionStateModel.setDrive('/dev/disk5');
|
SelectionStateModel.setDrive({
|
||||||
|
device: '/dev/disk5',
|
||||||
|
name: 'USB Drive',
|
||||||
|
size: 999999999
|
||||||
|
});
|
||||||
|
|
||||||
const drive = SelectionStateModel.getDrive();
|
const drive = SelectionStateModel.getDrive();
|
||||||
m.chai.expect(drive).to.equal('/dev/disk5');
|
m.chai.expect(drive).to.deep.equal({
|
||||||
|
device: '/dev/disk5',
|
||||||
|
name: 'USB Drive',
|
||||||
|
size: 999999999
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -114,9 +117,75 @@ describe('Browser: SelectionState', function() {
|
|||||||
describe('.setDrive()', function() {
|
describe('.setDrive()', function() {
|
||||||
|
|
||||||
it('should be able to set a drive', function() {
|
it('should be able to set a drive', function() {
|
||||||
SelectionStateModel.setDrive('/dev/disk5');
|
SelectionStateModel.setDrive({
|
||||||
|
device: '/dev/disk5',
|
||||||
|
name: 'USB Drive',
|
||||||
|
size: 999999999
|
||||||
|
});
|
||||||
|
|
||||||
const drive = SelectionStateModel.getDrive();
|
const drive = SelectionStateModel.getDrive();
|
||||||
m.chai.expect(drive).to.equal('/dev/disk5');
|
m.chai.expect(drive).to.deep.equal({
|
||||||
|
device: '/dev/disk5',
|
||||||
|
name: 'USB Drive',
|
||||||
|
size: 999999999
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if no device', function() {
|
||||||
|
m.chai.expect(function() {
|
||||||
|
SelectionStateModel.setDrive({
|
||||||
|
name: 'USB Drive',
|
||||||
|
size: 999999999
|
||||||
|
});
|
||||||
|
}).to.throw('Missing drive device');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if device is not a string', function() {
|
||||||
|
m.chai.expect(function() {
|
||||||
|
SelectionStateModel.setDrive({
|
||||||
|
device: 123,
|
||||||
|
name: 'USB Drive',
|
||||||
|
size: 999999999
|
||||||
|
});
|
||||||
|
}).to.throw('Invalid drive device: 123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if no name', function() {
|
||||||
|
m.chai.expect(function() {
|
||||||
|
SelectionStateModel.setDrive({
|
||||||
|
device: '/dev/disk2',
|
||||||
|
size: 999999999
|
||||||
|
});
|
||||||
|
}).to.throw('Missing drive name');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if name is not a string', function() {
|
||||||
|
m.chai.expect(function() {
|
||||||
|
SelectionStateModel.setDrive({
|
||||||
|
device: '/dev/disk2',
|
||||||
|
name: 123,
|
||||||
|
size: 999999999
|
||||||
|
});
|
||||||
|
}).to.throw('Invalid drive name: 123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if no size', function() {
|
||||||
|
m.chai.expect(function() {
|
||||||
|
SelectionStateModel.setDrive({
|
||||||
|
device: '/dev/disk2',
|
||||||
|
name: 'USB Drive'
|
||||||
|
});
|
||||||
|
}).to.throw('Missing drive size');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if size is not a number', function() {
|
||||||
|
m.chai.expect(function() {
|
||||||
|
SelectionStateModel.setDrive({
|
||||||
|
device: '/dev/disk2',
|
||||||
|
name: 'USB Drive',
|
||||||
|
size: '999999999'
|
||||||
|
});
|
||||||
|
}).to.throw('Invalid drive size: 999999999');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -126,14 +195,74 @@ describe('Browser: SelectionState', function() {
|
|||||||
describe('given an image', function() {
|
describe('given an image', function() {
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
SelectionStateModel.setImage('foo.img');
|
SelectionStateModel.setImage({
|
||||||
|
path: 'foo.img',
|
||||||
|
size: 999999999
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('.getImage()', function() {
|
describe('.isDriveLargeEnough()', function() {
|
||||||
|
|
||||||
it('should return the image', function() {
|
it('should return true if the drive size is greater than the image size', function() {
|
||||||
const image = SelectionStateModel.getImage();
|
const result = SelectionStateModel.isDriveLargeEnough({
|
||||||
m.chai.expect(image).to.equal('foo.img');
|
device: '/dev/disk1',
|
||||||
|
name: 'USB Drive',
|
||||||
|
size: 99999999999999
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
m.chai.expect(result).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.setDrive()', function() {
|
||||||
|
|
||||||
|
it('should throw if drive is no large enough', function() {
|
||||||
|
m.chai.expect(function() {
|
||||||
|
SelectionStateModel.setDrive({
|
||||||
|
device: '/dev/disk1',
|
||||||
|
name: 'USB Drive',
|
||||||
|
size: 999999998
|
||||||
|
});
|
||||||
|
}).to.throw('The drive is not large enough');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.getImagePath()', function() {
|
||||||
|
|
||||||
|
it('should return the image path', function() {
|
||||||
|
const imagePath = SelectionStateModel.getImagePath();
|
||||||
|
m.chai.expect(imagePath).to.equal('foo.img');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.getImageSize()', function() {
|
||||||
|
|
||||||
|
it('should return the image size', function() {
|
||||||
|
const imageSize = SelectionStateModel.getImageSize();
|
||||||
|
m.chai.expect(imageSize).to.equal(999999999);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -150,9 +279,15 @@ describe('Browser: SelectionState', function() {
|
|||||||
describe('.setImage()', function() {
|
describe('.setImage()', function() {
|
||||||
|
|
||||||
it('should override the image', function() {
|
it('should override the image', function() {
|
||||||
SelectionStateModel.setImage('bar.img');
|
SelectionStateModel.setImage({
|
||||||
const image = SelectionStateModel.getImage();
|
path: 'bar.img',
|
||||||
m.chai.expect(image).to.equal('bar.img');
|
size: 999999999
|
||||||
|
});
|
||||||
|
|
||||||
|
const imagePath = SelectionStateModel.getImagePath();
|
||||||
|
m.chai.expect(imagePath).to.equal('bar.img');
|
||||||
|
const imageSize = SelectionStateModel.getImageSize();
|
||||||
|
m.chai.expect(imageSize).to.equal(999999999);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -161,8 +296,11 @@ describe('Browser: SelectionState', function() {
|
|||||||
|
|
||||||
it('should clear the image', function() {
|
it('should clear the image', function() {
|
||||||
SelectionStateModel.removeImage();
|
SelectionStateModel.removeImage();
|
||||||
const image = SelectionStateModel.getImage();
|
|
||||||
m.chai.expect(image).to.be.undefined;
|
const imagePath = SelectionStateModel.getImagePath();
|
||||||
|
m.chai.expect(imagePath).to.be.undefined;
|
||||||
|
const imageSize = SelectionStateModel.getImageSize();
|
||||||
|
m.chai.expect(imageSize).to.be.undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -171,12 +309,66 @@ describe('Browser: SelectionState', function() {
|
|||||||
|
|
||||||
describe('given no image', 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('.setImage()', function() {
|
describe('.setImage()', function() {
|
||||||
|
|
||||||
it('should be able to set an image', function() {
|
it('should be able to set an image', function() {
|
||||||
SelectionStateModel.setImage('foo.img');
|
SelectionStateModel.setImage({
|
||||||
const image = SelectionStateModel.getImage();
|
path: 'foo.img',
|
||||||
m.chai.expect(image).to.equal('foo.img');
|
size: 999999999
|
||||||
|
});
|
||||||
|
|
||||||
|
const imagePath = SelectionStateModel.getImagePath();
|
||||||
|
m.chai.expect(imagePath).to.equal('foo.img');
|
||||||
|
const imageSize = SelectionStateModel.getImageSize();
|
||||||
|
m.chai.expect(imageSize).to.equal(999999999);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if no path', function() {
|
||||||
|
m.chai.expect(function() {
|
||||||
|
SelectionStateModel.setImage({
|
||||||
|
size: 999999999
|
||||||
|
});
|
||||||
|
}).to.throw('Missing image path');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if path is not a string', function() {
|
||||||
|
m.chai.expect(function() {
|
||||||
|
SelectionStateModel.setImage({
|
||||||
|
path: 123,
|
||||||
|
size: 999999999
|
||||||
|
});
|
||||||
|
}).to.throw('Invalid image path: 123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if no size', function() {
|
||||||
|
m.chai.expect(function() {
|
||||||
|
SelectionStateModel.setImage({
|
||||||
|
path: 'foo.img'
|
||||||
|
});
|
||||||
|
}).to.throw('Missing image size');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if size is not a number', function() {
|
||||||
|
m.chai.expect(function() {
|
||||||
|
SelectionStateModel.setImage({
|
||||||
|
path: 'foo.img',
|
||||||
|
size: '999999999'
|
||||||
|
});
|
||||||
|
}).to.throw('Invalid image size: 999999999');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -186,8 +378,16 @@ describe('Browser: SelectionState', function() {
|
|||||||
describe('given a drive', function() {
|
describe('given a drive', function() {
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
SelectionStateModel.setDrive('/dev/disk2');
|
SelectionStateModel.setDrive({
|
||||||
SelectionStateModel.setImage('foo.img');
|
device: '/dev/disk1',
|
||||||
|
name: 'USB Drive',
|
||||||
|
size: 999999999
|
||||||
|
});
|
||||||
|
|
||||||
|
SelectionStateModel.setImage({
|
||||||
|
path: 'foo.img',
|
||||||
|
size: 999999999
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('.clear()', function() {
|
describe('.clear()', function() {
|
||||||
@ -217,9 +417,14 @@ describe('Browser: SelectionState', function() {
|
|||||||
m.chai.expect(drive).to.be.undefined;
|
m.chai.expect(drive).to.be.undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getImage() should return the image', function() {
|
it('getImagePath() should return the image path', function() {
|
||||||
const image = SelectionStateModel.getImage();
|
const imagePath = SelectionStateModel.getImagePath();
|
||||||
m.chai.expect(image).to.equal('foo.img');
|
m.chai.expect(imagePath).to.equal('foo.img');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getImageSize() should return the image size', function() {
|
||||||
|
const imageSize = SelectionStateModel.getImageSize();
|
||||||
|
m.chai.expect(imageSize).to.equal(999999999);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hasDrive() should return false', function() {
|
it('hasDrive() should return false', function() {
|
||||||
@ -244,7 +449,7 @@ describe('Browser: SelectionState', function() {
|
|||||||
SelectionStateModel.setDrive({
|
SelectionStateModel.setDrive({
|
||||||
device: '/dev/sdb',
|
device: '/dev/sdb',
|
||||||
description: 'DataTraveler 2.0',
|
description: 'DataTraveler 2.0',
|
||||||
size: '7.3G',
|
size: 999999999,
|
||||||
mountpoint: '/media/UNTITLED',
|
mountpoint: '/media/UNTITLED',
|
||||||
name: '/dev/sdb',
|
name: '/dev/sdb',
|
||||||
system: false
|
system: false
|
||||||
@ -263,7 +468,7 @@ describe('Browser: SelectionState', function() {
|
|||||||
m.chai.expect(SelectionStateModel.isCurrentDrive({
|
m.chai.expect(SelectionStateModel.isCurrentDrive({
|
||||||
device: '/dev/sdb',
|
device: '/dev/sdb',
|
||||||
description: 'DataTraveler 2.0',
|
description: 'DataTraveler 2.0',
|
||||||
size: '7.3G',
|
size: 999999999,
|
||||||
mountpoint: '/media/UNTITLED',
|
mountpoint: '/media/UNTITLED',
|
||||||
name: '/dev/sdb',
|
name: '/dev/sdb',
|
||||||
system: false
|
system: false
|
||||||
@ -274,7 +479,7 @@ describe('Browser: SelectionState', function() {
|
|||||||
m.chai.expect(SelectionStateModel.isCurrentDrive({
|
m.chai.expect(SelectionStateModel.isCurrentDrive({
|
||||||
device: '/dev/sdb',
|
device: '/dev/sdb',
|
||||||
description: 'DataTraveler 2.0',
|
description: 'DataTraveler 2.0',
|
||||||
size: '7.3G',
|
size: 999999999,
|
||||||
mountpoint: '/media/UNTITLED',
|
mountpoint: '/media/UNTITLED',
|
||||||
name: '/dev/sdb',
|
name: '/dev/sdb',
|
||||||
system: false,
|
system: false,
|
||||||
@ -286,7 +491,7 @@ describe('Browser: SelectionState', function() {
|
|||||||
m.chai.expect(SelectionStateModel.isCurrentDrive({
|
m.chai.expect(SelectionStateModel.isCurrentDrive({
|
||||||
device: '/dev/sdc',
|
device: '/dev/sdc',
|
||||||
description: 'DataTraveler 2.0',
|
description: 'DataTraveler 2.0',
|
||||||
size: '7.3G',
|
size: 999999999,
|
||||||
mountpoint: '/media/UNTITLED',
|
mountpoint: '/media/UNTITLED',
|
||||||
name: '/dev/sdb',
|
name: '/dev/sdb',
|
||||||
system: false
|
system: false
|
||||||
@ -297,7 +502,7 @@ describe('Browser: SelectionState', function() {
|
|||||||
m.chai.expect(SelectionStateModel.isCurrentDrive({
|
m.chai.expect(SelectionStateModel.isCurrentDrive({
|
||||||
device: '/dev/sdb',
|
device: '/dev/sdb',
|
||||||
description: 'DataTraveler 3.0',
|
description: 'DataTraveler 3.0',
|
||||||
size: '7.3G',
|
size: 999999999,
|
||||||
mountpoint: '/media/UNTITLED',
|
mountpoint: '/media/UNTITLED',
|
||||||
name: '/dev/sdb',
|
name: '/dev/sdb',
|
||||||
system: false
|
system: false
|
||||||
@ -325,7 +530,7 @@ describe('Browser: SelectionState', function() {
|
|||||||
m.chai.expect(SelectionStateModel.isCurrentDrive({
|
m.chai.expect(SelectionStateModel.isCurrentDrive({
|
||||||
device: '/dev/sdb',
|
device: '/dev/sdb',
|
||||||
description: 'DataTraveler 2.0',
|
description: 'DataTraveler 2.0',
|
||||||
size: '7.3G',
|
size: 999999999,
|
||||||
mountpoint: '/media/UNTITLED',
|
mountpoint: '/media/UNTITLED',
|
||||||
name: '/dev/sdb',
|
name: '/dev/sdb',
|
||||||
system: false
|
system: false
|
||||||
@ -345,7 +550,7 @@ describe('Browser: SelectionState', function() {
|
|||||||
this.drive = {
|
this.drive = {
|
||||||
device: '/dev/sdb',
|
device: '/dev/sdb',
|
||||||
description: 'DataTraveler 2.0',
|
description: 'DataTraveler 2.0',
|
||||||
size: '7.3G',
|
size: 999999999,
|
||||||
mountpoint: '/media/UNTITLED',
|
mountpoint: '/media/UNTITLED',
|
||||||
name: '/dev/sdb',
|
name: '/dev/sdb',
|
||||||
system: false
|
system: false
|
||||||
@ -364,7 +569,7 @@ describe('Browser: SelectionState', function() {
|
|||||||
const drive = {
|
const drive = {
|
||||||
device: '/dev/disk2',
|
device: '/dev/disk2',
|
||||||
name: 'USB Drive',
|
name: 'USB Drive',
|
||||||
size: '16GB'
|
size: 999999999
|
||||||
};
|
};
|
||||||
|
|
||||||
m.chai.expect(SelectionStateModel.getDrive()).to.deep.equal(this.drive);
|
m.chai.expect(SelectionStateModel.getDrive()).to.deep.equal(this.drive);
|
||||||
@ -385,7 +590,7 @@ describe('Browser: SelectionState', function() {
|
|||||||
const drive = {
|
const drive = {
|
||||||
device: '/dev/disk2',
|
device: '/dev/disk2',
|
||||||
name: 'USB Drive',
|
name: 'USB Drive',
|
||||||
size: '16GB'
|
size: 999999999
|
||||||
};
|
};
|
||||||
|
|
||||||
m.chai.expect(SelectionStateModel.hasDrive()).to.be.false;
|
m.chai.expect(SelectionStateModel.hasDrive()).to.be.false;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user