mirror of
https://github.com/balena-io/etcher.git
synced 2025-04-24 07:17:18 +00:00
feat(GUI): image-defined recommended drive size (#703)
Recently, we've added support for a `recommendedDriveSize` property in the `manifest.json` of extended image archives, which the image can use to warn the user that his drive, even if it is large enough to hold the image, might not be large enough to deliver a good usage experience later on. When this property is found, the GUI reacts in the following ways: - Drives that are large enough to hold the image but don't meet the recommended drive size are tagged with a warning label in the drive selector component. - Attempting to select a "labeled" drive opens a warning modal asking for user confirmation. - Drives that don't meet the recommended drive size declared in the image won't get auto-selected. - If there is a drive already selected, and the user picks an image whose recommended drive size is greater than the drive size, the currently selected drive gets auto-deselected. Code-wise, the following significant changes have been introduced: - Implement `SelectionStateModel.getImageRecommendedDriveSize()`. - Implement `SelectionStateModel.isDriveSizeRecommended()`. - Extract `WarningModal` out of the settings page (the dangerous setting modal). Change-Type: minor Changelog-Entry: Allow images to declare a recommended minimum drive size. See: https://github.com/resin-io-modules/etcher-image-stream/pull/36 Fixes: https://github.com/resin-io/etcher/issues/698 Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
This commit is contained in:
parent
7ea098c0d6
commit
401cdb6f52
@ -18,7 +18,7 @@
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = function($uibModalInstance, DrivesModel, SelectionStateModel) {
|
||||
module.exports = function($uibModalInstance, DrivesModel, SelectionStateModel, WarningModalService) {
|
||||
|
||||
/**
|
||||
* @summary The drive selector state
|
||||
@ -40,6 +40,44 @@ module.exports = function($uibModalInstance, DrivesModel, SelectionStateModel) {
|
||||
*/
|
||||
this.drives = DrivesModel;
|
||||
|
||||
/**
|
||||
* @summary Toggle a drive selection
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {Object} drive - drive
|
||||
*
|
||||
* @example
|
||||
* DriveSelectorController.toggleDrive({
|
||||
* device: '/dev/disk2',
|
||||
* size: 999999999,
|
||||
* name: 'Cruzer USB drive'
|
||||
* });
|
||||
*/
|
||||
this.toggleDrive = (drive) => {
|
||||
if (!SelectionStateModel.isDriveValid(drive)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_.some([
|
||||
SelectionStateModel.isDriveSizeRecommended(drive),
|
||||
SelectionStateModel.isCurrentDrive(drive.device)
|
||||
])) {
|
||||
SelectionStateModel.toggleSetDrive(drive.device);
|
||||
return;
|
||||
}
|
||||
|
||||
WarningModalService.display([
|
||||
`This image recommends a ${SelectionStateModel.getImageRecommendedDriveSize()}`,
|
||||
`bytes drive, however ${drive.device} is only ${drive.size} bytes.`,
|
||||
'Are you sure you want to continue?'
|
||||
].join(' ')).then((userAccepted) => {
|
||||
if (userAccepted) {
|
||||
SelectionStateModel.toggleSetDrive(drive.device);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Close the modal and resolve the selected drive
|
||||
* @function
|
||||
@ -55,7 +93,6 @@ module.exports = function($uibModalInstance, DrivesModel, SelectionStateModel) {
|
||||
// the drive is then unplugged from the computer and the modal
|
||||
// is resolved with a non-existent drive.
|
||||
if (!selectedDrive || !_.includes(this.drives.getDrives(), selectedDrive)) {
|
||||
|
||||
$uibModalInstance.close();
|
||||
} else {
|
||||
$uibModalInstance.close(selectedDrive);
|
||||
|
@ -24,6 +24,7 @@ const angular = require('angular');
|
||||
const MODULE_NAME = 'Etcher.Components.DriveSelector';
|
||||
const DriveSelector = angular.module(MODULE_NAME, [
|
||||
require('../modal/modal'),
|
||||
require('../warning-modal/warning-modal'),
|
||||
require('../../models/drives'),
|
||||
require('../../models/selection-state'),
|
||||
require('../../utils/byte-size/byte-size')
|
||||
|
@ -7,13 +7,18 @@
|
||||
<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-click="modal.state.isDriveValid(drive) && modal.state.toggleSetDrive(drive.device)">
|
||||
ng-click="modal.toggleDrive(drive)">
|
||||
<div>
|
||||
<h4 class="list-group-item-heading">{{ drive.description }} - {{ drive.size | gigabyte | number:1 }} GB</h4>
|
||||
<p class="list-group-item-text">{{ drive.name }}</p>
|
||||
|
||||
<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)">
|
||||
<i class="glyphicon glyphicon-warning-sign"></i>
|
||||
NOT RECOMMENDED</span>
|
||||
|
||||
<!-- There can be a case where the device it not large enough, and it's also locked. -->
|
||||
<!-- Since in this case both labels will be displayed, we chose to only show the -->
|
||||
<!-- "not large enough label", since from the point of view of the user, the locked -->
|
||||
|
@ -26,24 +26,24 @@ module.exports = function($uibModalInstance, message) {
|
||||
this.message = message;
|
||||
|
||||
/**
|
||||
* @summary Reject the dangerous setting
|
||||
* @summary Reject the warning prompt
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* SettingsDangerousModalController.reject();
|
||||
* WarningModalController.reject();
|
||||
*/
|
||||
this.reject = () => {
|
||||
$uibModalInstance.close(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Accept the dangerous setting
|
||||
* @summary Accept the warning prompt
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* SettingsDangerousModalController.accept();
|
||||
* WarningModalController.accept();
|
||||
*/
|
||||
this.accept = () => {
|
||||
$uibModalInstance.close(true);
|
46
lib/gui/components/warning-modal/services/warning-modal.js
Normal file
46
lib/gui/components/warning-modal/services/warning-modal.js
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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';
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = function(ModalService) {
|
||||
|
||||
/**
|
||||
* @summary Display the warning modal
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {String} message - danger message
|
||||
* @fulfil {Boolean} - whether the user accepted or rejected the warning
|
||||
* @returns {Promise}
|
||||
*
|
||||
* @example
|
||||
* WarningModalService.display('Don\'t do this!');
|
||||
*/
|
||||
this.display = (message) => {
|
||||
return ModalService.open({
|
||||
template: './components/warning-modal/templates/warning-modal.tpl.html',
|
||||
controller: 'WarningModalController as modal',
|
||||
size: 'settings-dangerous-modal',
|
||||
resolve: {
|
||||
message: _.constant(message)
|
||||
}
|
||||
}).result;
|
||||
};
|
||||
|
||||
};
|
19
lib/gui/components/warning-modal/styles/_warning-modal.scss
Normal file
19
lib/gui/components/warning-modal/styles/_warning-modal.scss
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.modal-warning-modal .modal-title .glyphicon {
|
||||
color: $palette-theme-danger-background;
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="modal-text">
|
||||
<p>Are you sure you want to turn this on? {{ modal.message }}</p>
|
||||
<p>{{ ::modal.message }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
32
lib/gui/components/warning-modal/warning-modal.js
Normal file
32
lib/gui/components/warning-modal/warning-modal.js
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.WarningModal
|
||||
*/
|
||||
|
||||
const angular = require('angular');
|
||||
const MODULE_NAME = 'Etcher.Components.WarningModal';
|
||||
const WarningModal = angular.module(MODULE_NAME, [
|
||||
require('../modal/modal')
|
||||
]);
|
||||
|
||||
WarningModal.controller('WarningModalController', require('./controllers/warning-modal'));
|
||||
WarningModal.service('WarningModalService', require('./services/warning-modal'));
|
||||
|
||||
module.exports = MODULE_NAME;
|
@ -81,6 +81,37 @@ SelectionStateModel.service('SelectionStateModel', function(DrivesModel) {
|
||||
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
|
||||
@ -274,6 +305,20 @@ SelectionStateModel.service('SelectionStateModel', function(DrivesModel) {
|
||||
return _.get(Store.getState().toJS(), 'selection.image.supportUrl');
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Get image recommended drive size
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {String} image recommended drive size
|
||||
*
|
||||
* @example
|
||||
* const imageRecommendedDriveSize = SelectionStateModel.getImageRecommendedDriveSize();
|
||||
*/
|
||||
this.getImageRecommendedDriveSize = () => {
|
||||
return _.get(Store.getState().toJS(), 'selection.image.recommendedDriveSize');
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Check if there is a selected drive
|
||||
* @function
|
||||
|
@ -94,8 +94,16 @@ const storeReducer = (state, action) => {
|
||||
|
||||
const drive = _.first(action.data);
|
||||
|
||||
// TODO: Reuse from SelectionStateModel.isDriveValid()
|
||||
if (state.getIn([ 'selection', 'image', 'size' ], 0) <= drive.size && !drive.protected) {
|
||||
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
|
||||
])) {
|
||||
return storeReducer(newState, {
|
||||
type: ACTIONS.SELECT_DRIVE,
|
||||
data: drive.device
|
||||
@ -260,7 +268,10 @@ const storeReducer = (state, action) => {
|
||||
});
|
||||
|
||||
return _.attempt(() => {
|
||||
if (selectedDrive && selectedDrive.get('size', 0) < action.data.size) {
|
||||
if (_.some([
|
||||
selectedDrive && selectedDrive.get('size', 0) < action.data.size,
|
||||
selectedDrive && selectedDrive.get('size', 0) < action.data.recommendedDriveSize
|
||||
])) {
|
||||
return storeReducer(state, {
|
||||
type: ACTIONS.REMOVE_DRIVE
|
||||
});
|
||||
|
@ -16,9 +16,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = function(ModalService, SettingsModel) {
|
||||
module.exports = function(WarningModalService, SettingsModel) {
|
||||
|
||||
/**
|
||||
* @summary Refresh current settings
|
||||
@ -62,14 +60,7 @@ module.exports = function(ModalService, SettingsModel) {
|
||||
return this.refreshSettings();
|
||||
}
|
||||
|
||||
ModalService.open({
|
||||
template: './pages/settings/templates/settings-dangerous-modal.tpl.html',
|
||||
controller: 'SettingsDangerousModalController as modal',
|
||||
size: 'settings-dangerous-modal',
|
||||
resolve: {
|
||||
message: _.constant(message)
|
||||
}
|
||||
}).result.then((userAccepted) => {
|
||||
WarningModalService.display(message).then((userAccepted) => {
|
||||
this.model.set(name, Boolean(userAccepted));
|
||||
this.refreshSettings();
|
||||
});
|
||||
|
@ -24,12 +24,11 @@ const angular = require('angular');
|
||||
const MODULE_NAME = 'Etcher.Pages.Settings';
|
||||
const SettingsPage = angular.module(MODULE_NAME, [
|
||||
require('angular-ui-router'),
|
||||
require('../../components/modal/modal'),
|
||||
require('../../components/warning-modal/warning-modal'),
|
||||
require('../../models/settings')
|
||||
]);
|
||||
|
||||
SettingsPage.controller('SettingsController', require('./controllers/settings'));
|
||||
SettingsPage.controller('SettingsDangerousModalController', require('./controllers/settings-dangerous-modal'));
|
||||
|
||||
SettingsPage.config(($stateProvider) => {
|
||||
$stateProvider
|
||||
|
@ -31,7 +31,3 @@
|
||||
margin-top: 30px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.modal-settings-dangerous-modal .modal-title .glyphicon {
|
||||
color: $palette-theme-danger-background;
|
||||
}
|
||||
|
@ -37,7 +37,7 @@
|
||||
<label>
|
||||
<input type="checkbox"
|
||||
ng-model="settings.currentData.unsafeMode"
|
||||
ng-change="settings.enableDangerousSetting('unsafeMode', 'You will be able to burn to your system drives.')">
|
||||
ng-change="settings.enableDangerousSetting('unsafeMode', 'Are you sure you want to turn this on? You will be able to burn to your system drives.')">
|
||||
|
||||
<span>Unsafe mode <span class="label label-danger">DANGEROUS</span></span>
|
||||
</label>
|
||||
|
@ -36,6 +36,7 @@ $link-color: #ddd;
|
||||
@import "../components/drive-selector/styles/drive-selector";
|
||||
@import "../components/tooltip-modal/styles/tooltip-modal";
|
||||
@import "../components/flash-error-modal/styles/flash-error-modal";
|
||||
@import "../components/warning-modal/styles/warning-modal";
|
||||
@import "../pages/main/styles/main";
|
||||
@import "../pages/settings/styles/settings";
|
||||
@import "../pages/finish/styles/finish";
|
||||
|
@ -111,7 +111,8 @@ describe('Browser: DrivesModel', function() {
|
||||
SelectionStateModel.removeDrive();
|
||||
SelectionStateModel.setImage({
|
||||
path: 'foo.img',
|
||||
size: 999999999
|
||||
size: 999999999,
|
||||
recommendedDriveSize: 2000000000
|
||||
});
|
||||
});
|
||||
|
||||
@ -151,7 +152,7 @@ describe('Browser: DrivesModel', function() {
|
||||
{
|
||||
device: '/dev/sdb',
|
||||
name: 'Foo',
|
||||
size: 999999999,
|
||||
size: 2000000000,
|
||||
mountpoint: '/mnt/foo',
|
||||
system: false,
|
||||
protected: false
|
||||
@ -161,7 +162,7 @@ describe('Browser: DrivesModel', function() {
|
||||
m.chai.expect(SelectionStateModel.getDrive()).to.deep.equal({
|
||||
device: '/dev/sdb',
|
||||
name: 'Foo',
|
||||
size: 999999999,
|
||||
size: 2000000000,
|
||||
mountpoint: '/mnt/foo',
|
||||
system: false,
|
||||
protected: false
|
||||
@ -185,6 +186,23 @@ describe('Browser: DrivesModel', function() {
|
||||
m.chai.expect(SelectionStateModel.hasDrive()).to.be.false;
|
||||
});
|
||||
|
||||
it('should not auto-select a single drive that doesn\'t meet the recommended size', function() {
|
||||
m.chai.expect(SelectionStateModel.hasDrive()).to.be.false;
|
||||
|
||||
DrivesModel.setDrives([
|
||||
{
|
||||
device: '/dev/sdb',
|
||||
name: 'Foo',
|
||||
size: 1500000000,
|
||||
mountpoint: '/mnt/foo',
|
||||
system: false,
|
||||
protected: false
|
||||
}
|
||||
]);
|
||||
|
||||
m.chai.expect(SelectionStateModel.hasDrive()).to.be.false;
|
||||
});
|
||||
|
||||
it('should not auto-select a single protected drive', function() {
|
||||
m.chai.expect(SelectionStateModel.hasDrive()).to.be.false;
|
||||
|
||||
|
@ -59,6 +59,10 @@ describe('Browser: SelectionState', function() {
|
||||
m.chai.expect(SelectionStateModel.getImageSupportUrl()).to.be.undefined;
|
||||
});
|
||||
|
||||
it('getImageRecommendedDriveSize() should return undefined', function() {
|
||||
m.chai.expect(SelectionStateModel.getImageRecommendedDriveSize()).to.be.undefined;
|
||||
});
|
||||
|
||||
it('hasDrive() should return false', function() {
|
||||
const hasDrive = SelectionStateModel.hasDrive();
|
||||
m.chai.expect(hasDrive).to.be.false;
|
||||
@ -274,6 +278,7 @@ describe('Browser: SelectionState', function() {
|
||||
SelectionStateModel.setImage({
|
||||
path: 'foo.img',
|
||||
size: 999999999,
|
||||
recommendedDriveSize: 1000000000,
|
||||
url: 'https://www.raspbian.org',
|
||||
supportUrl: 'https://www.raspbian.org/forums/',
|
||||
name: 'Raspbian',
|
||||
@ -318,6 +323,43 @@ describe('Browser: SelectionState', function() {
|
||||
|
||||
});
|
||||
|
||||
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() {
|
||||
@ -439,6 +481,15 @@ describe('Browser: SelectionState', function() {
|
||||
|
||||
});
|
||||
|
||||
describe('.getImageRecommendedDriveSize()', function() {
|
||||
|
||||
it('should return the image recommended drive size', function() {
|
||||
const imageRecommendedDriveSize = SelectionStateModel.getImageRecommendedDriveSize();
|
||||
m.chai.expect(imageRecommendedDriveSize).to.equal(1000000000);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.hasImage()', function() {
|
||||
|
||||
it('should return true', function() {
|
||||
@ -495,6 +546,20 @@ describe('Browser: SelectionState', function() {
|
||||
|
||||
});
|
||||
|
||||
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() {
|
||||
@ -595,6 +660,29 @@ describe('Browser: SelectionState', function() {
|
||||
SelectionStateModel.removeImage();
|
||||
});
|
||||
|
||||
it('should de-select a previously selected not-recommended drive', function() {
|
||||
DrivesModel.setDrives([
|
||||
{
|
||||
device: '/dev/disk1',
|
||||
name: 'USB Drive',
|
||||
size: 1200000000,
|
||||
protected: false
|
||||
}
|
||||
]);
|
||||
|
||||
SelectionStateModel.setDrive('/dev/disk1');
|
||||
m.chai.expect(SelectionStateModel.hasDrive()).to.be.true;
|
||||
|
||||
SelectionStateModel.setImage({
|
||||
path: 'foo.img',
|
||||
size: 999999999,
|
||||
recommendedDriveSize: 1500000000
|
||||
});
|
||||
|
||||
m.chai.expect(SelectionStateModel.hasDrive()).to.be.false;
|
||||
SelectionStateModel.removeImage();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user