mirror of
https://github.com/balena-io/etcher.git
synced 2025-04-24 15:27:17 +00:00
feat: add drive multi-selection in store (#1736)
We lay the foundation for multi-selecting drives by implementing it into the `store` and relevant modules interacting with the `store`. Change-Type: patch Changelog-Entry: Add drive multi-selection to the store.
This commit is contained in:
parent
ee93013220
commit
207c2ef5b6
@ -118,6 +118,7 @@ module.exports = function (
|
||||
previouslySelected: selectionState.isCurrentDrive(drive.device)
|
||||
})
|
||||
|
||||
selectionState.deselectOtherDrives(drive.device)
|
||||
selectionState.toggleDrive(drive.device)
|
||||
}
|
||||
})
|
||||
@ -132,7 +133,7 @@ module.exports = function (
|
||||
* DriveSelectorController.closeModal();
|
||||
*/
|
||||
this.closeModal = () => {
|
||||
const selectedDrive = selectionState.getDrive()
|
||||
const selectedDrive = selectionState.getCurrentDrive()
|
||||
|
||||
// Sanity check to cover the case where a drive is selected,
|
||||
// the drive is then unplugged from the computer and the modal
|
||||
|
@ -38,7 +38,7 @@
|
||||
</div>
|
||||
<span class="list-group-item-section tick tick--success"
|
||||
ng-show="modal.constraints.isDriveValid(drive, modal.state.getImage())"
|
||||
ng-disabled="!modal.state.isCurrentDrive(drive.device)"></span>
|
||||
ng-disabled="!modal.state.isDriveSelected(drive.device)"></span>
|
||||
</li>
|
||||
<li class="list-group-item"
|
||||
ng-show="!modal.drives.hasAvailableDrives()">
|
||||
|
@ -51,7 +51,7 @@ module.exports = function ($state) {
|
||||
if (!options.preserveImage) {
|
||||
selectionState.deselectImage()
|
||||
}
|
||||
selectionState.deselectDrive()
|
||||
selectionState.deselectAllDrives()
|
||||
analytics.logEvent('Restart', options)
|
||||
$state.go('main')
|
||||
}
|
||||
|
@ -66,11 +66,11 @@
|
||||
'text-disabled': main.shouldDriveStepBeDisabled()
|
||||
}">
|
||||
<span class="drive-step step-name"
|
||||
uib-tooltip="{{ main.selection.getDrive().description }} ({{ main.selection.getDrive().displayName }})">
|
||||
uib-tooltip="{{ main.selection.getCurrentDrive().description }} ({{ main.selection.getCurrentDrive().displayName }})">
|
||||
<!-- middleEllipses errors on undefined, therefore fallback to empty string -->
|
||||
{{ (main.selection.getDrive().description || "") | middleEllipses:11 }}
|
||||
{{ (main.selection.getCurrentDrive().description || "") | middleEllipses:11 }}
|
||||
</span>
|
||||
<span class="step-drive step-size">{{ main.selection.getDrive().size | closestUnit }}</span>
|
||||
<span class="step-drive step-size">{{ main.selection.getCurrentDrive().size | closestUnit }}</span>
|
||||
<span class="step-drive step-warning glyphicon glyphicon-exclamation-sign"
|
||||
uib-tooltip="{{ main.constraints.getDriveImageCompatibilityStatuses(main.selection.getDrive(), main.selection.getImage())[0].message }}"
|
||||
ng-show="main.constraints.hasDriveImageCompatibilityStatus(main.selection.getDrive(), main.selection.getImage())"></span>
|
||||
@ -97,7 +97,7 @@
|
||||
percentage="main.state.getFlashState().percentage"
|
||||
striped="{{ main.state.getFlashState().type == 'check' }}"
|
||||
ng-attr-active="{{ main.state.isFlashing() }}"
|
||||
ng-click="flash.flashImageToDrive(main.selection.getImage(), main.selection.getDrive())"
|
||||
ng-click="flash.flashImageToDrive(main.selection.getImage(), main.selection.getCurrentDrive())"
|
||||
ng-disabled="main.shouldFlashStepBeDisabled() || main.state.getLastFlashErrorCode()">
|
||||
<span ng-bind="flash.getProgressButtonLabel()"></span>
|
||||
</progress-button>
|
||||
|
@ -48,13 +48,41 @@ exports.selectDrive = (driveDevice) => {
|
||||
* selectionState.toggleDrive('/dev/disk2');
|
||||
*/
|
||||
exports.toggleDrive = (driveDevice) => {
|
||||
if (exports.isCurrentDrive(driveDevice)) {
|
||||
exports.deselectDrive()
|
||||
if (exports.isDriveSelected(driveDevice)) {
|
||||
exports.deselectDrive(driveDevice)
|
||||
} else {
|
||||
exports.selectDrive(driveDevice)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Deselect all other drives and keep the current drive's status
|
||||
* @function
|
||||
* @public
|
||||
* @deprecated
|
||||
*
|
||||
* @description
|
||||
* This is a temporary function during the transition to multi-writes,
|
||||
* remove this and its uses when multi-selection should become user-facing.
|
||||
*
|
||||
* @param {String} driveDevice - drive device identifier
|
||||
*
|
||||
* @example
|
||||
* console.log(selectionState.getSelectedDevices())
|
||||
* > [ '/dev/disk1', '/dev/disk2', '/dev/disk3' ]
|
||||
* selectionState.deselectOtherDrives('/dev/disk2')
|
||||
* console.log(selectionState.getSelectedDevices())
|
||||
* > [ '/dev/disk2' ]
|
||||
*/
|
||||
exports.deselectOtherDrives = (driveDevice) => {
|
||||
if (exports.isDriveSelected(driveDevice)) {
|
||||
const otherDevices = _.reject(exports.getSelectedDevices(), _.partial(_.isEqual, driveDevice))
|
||||
_.each(otherDevices, exports.deselectDrive)
|
||||
} else {
|
||||
exports.deselectAllDrives()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Select an image
|
||||
* @function
|
||||
@ -82,19 +110,38 @@ exports.selectImage = (image) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get drive
|
||||
* @summary Get all selected drives' devices
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {String[]} selected drives' devices
|
||||
*
|
||||
* @example
|
||||
* for (driveDevice of selectionState.getSelectedDevices()) {
|
||||
* console.log(driveDevice)
|
||||
* }
|
||||
* > '/dev/disk1'
|
||||
* > '/dev/disk2'
|
||||
*/
|
||||
exports.getSelectedDevices = () => {
|
||||
return store.getState().getIn([ 'selection', 'devices' ]).toJS()
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get the head of the list of selected drives
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {Object} drive
|
||||
*
|
||||
* @example
|
||||
* const drive = selectionState.getDrive();
|
||||
* const drive = selectionState.getCurrentDrive();
|
||||
* console.log(drive)
|
||||
* > { device: '/dev/disk1', name: 'Flash drive', ... }
|
||||
*/
|
||||
exports.getDrive = () => {
|
||||
return _.find(availableDrives.getDrives(), {
|
||||
device: store.getState().getIn([ 'selection', 'drive' ])
|
||||
})
|
||||
exports.getCurrentDrive = () => {
|
||||
const device = _.head(exports.getSelectedDevices())
|
||||
return _.find(availableDrives.getDrives(), { device })
|
||||
}
|
||||
|
||||
/**
|
||||
@ -252,7 +299,7 @@ exports.getImageRecommendedDriveSize = () => {
|
||||
* }
|
||||
*/
|
||||
exports.hasDrive = () => {
|
||||
return Boolean(exports.getDrive())
|
||||
return Boolean(exports.getSelectedDevices().length)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -272,16 +319,22 @@ exports.hasImage = () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Remove drive
|
||||
* @summary Remove drive from selection
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {String} driveDevice - drive device identifier
|
||||
*
|
||||
* @example
|
||||
* selectionState.deselectDrive();
|
||||
* selectionState.deselectDrive('/dev/sdc');
|
||||
*
|
||||
* @example
|
||||
* selectionState.deselectDrive('\\\\.\\PHYSICALDRIVE3');
|
||||
*/
|
||||
exports.deselectDrive = () => {
|
||||
exports.deselectDrive = (driveDevice) => {
|
||||
store.dispatch({
|
||||
type: store.Actions.DESELECT_DRIVE
|
||||
type: store.Actions.DESELECT_DRIVE,
|
||||
data: driveDevice
|
||||
})
|
||||
}
|
||||
|
||||
@ -299,6 +352,18 @@ exports.deselectImage = () => {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Unselect all drives
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* selectionState.deselectAllDrives()
|
||||
*/
|
||||
exports.deselectAllDrives = () => {
|
||||
_.each(exports.getSelectedDevices(), exports.deselectDrive)
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Clear selections
|
||||
* @function
|
||||
@ -306,13 +371,10 @@ exports.deselectImage = () => {
|
||||
*
|
||||
* @example
|
||||
* selectionState.clear();
|
||||
*
|
||||
* @example
|
||||
* selectionState.clear({ preserveImage: true });
|
||||
*/
|
||||
exports.clear = () => {
|
||||
exports.deselectImage()
|
||||
exports.deselectDrive()
|
||||
exports.deselectAllDrives()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -333,5 +395,29 @@ exports.isCurrentDrive = (driveDevice) => {
|
||||
return false
|
||||
}
|
||||
|
||||
return driveDevice === _.get(exports.getDrive(), [ 'device' ])
|
||||
return driveDevice === _.get(exports.getCurrentDrive(), [ 'device' ])
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Check whether a given device is selected.
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {String} driveDevice - drive device identifier
|
||||
* @returns {Boolean}
|
||||
*
|
||||
* @example
|
||||
* const isSelected = selectionState.isDriveSelected('/dev/sdb')
|
||||
*
|
||||
* if (isSelected) {
|
||||
* selectionState.deselectDrive(driveDevice)
|
||||
* }
|
||||
*/
|
||||
exports.isDriveSelected = (driveDevice) => {
|
||||
if (!driveDevice) {
|
||||
return false
|
||||
}
|
||||
|
||||
const selectedDriveDevices = exports.getSelectedDevices()
|
||||
return _.includes(selectedDriveDevices, driveDevice)
|
||||
}
|
||||
|
@ -82,7 +82,9 @@ const selectImageNoNilFields = [
|
||||
*/
|
||||
const DEFAULT_STATE = Immutable.fromJS({
|
||||
availableDrives: [],
|
||||
selection: {},
|
||||
selection: {
|
||||
devices: new Immutable.OrderedSet()
|
||||
},
|
||||
isFlashing: false,
|
||||
flashResults: {},
|
||||
flashState: {
|
||||
@ -122,25 +124,20 @@ const ACTIONS = _.fromPairs(_.map([
|
||||
}))
|
||||
|
||||
/**
|
||||
* @summary Find a drive from the list of available drives
|
||||
* @summary Get available drives from the state
|
||||
* @function
|
||||
* @private
|
||||
* @public
|
||||
*
|
||||
* @param {Object} state - application state
|
||||
* @param {String} device - drive device
|
||||
* @returns {(Object|Undefined)} drive
|
||||
* @param {Object} state - state object
|
||||
* @returns {Object} new state
|
||||
*
|
||||
* @example
|
||||
* const drive = findDrive(state, '/dev/disk2');
|
||||
* const drives = getAvailableDrives(state)
|
||||
* _.find(drives, { device: '/dev/sda' })
|
||||
*/
|
||||
const findDrive = (state, device) => {
|
||||
/* eslint-disable lodash/prefer-lodash-method */
|
||||
|
||||
return state.get('availableDrives').find((drive) => {
|
||||
return drive.get('device') === device
|
||||
})
|
||||
|
||||
/* eslint-enable lodash/prefer-lodash-method */
|
||||
const getAvailableDrives = (state) => {
|
||||
// eslint-disable-next-line lodash/prefer-lodash-method
|
||||
return state.get('availableDrives').toJS()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -160,6 +157,8 @@ const findDrive = (state, device) => {
|
||||
const storeReducer = (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case ACTIONS.SET_AVAILABLE_DRIVES: {
|
||||
// Type: action.data : Array<DriveObject>
|
||||
|
||||
if (!action.data) {
|
||||
throw errors.createError({
|
||||
title: 'Missing drives'
|
||||
@ -167,20 +166,20 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
|
||||
}
|
||||
|
||||
// Convert object instances to plain objects
|
||||
action.data = JSON.parse(JSON.stringify(action.data))
|
||||
const drives = JSON.parse(JSON.stringify(action.data))
|
||||
|
||||
if (!_.isArray(action.data) || !_.every(action.data, _.isPlainObject)) {
|
||||
if (!_.isArray(drives) || !_.every(drives, _.isPlainObject)) {
|
||||
throw errors.createError({
|
||||
title: `Invalid drives: ${action.data}`
|
||||
title: `Invalid drives: ${drives}`
|
||||
})
|
||||
}
|
||||
|
||||
const newState = state.set('availableDrives', Immutable.fromJS(action.data))
|
||||
const newState = state.set('availableDrives', Immutable.fromJS(drives))
|
||||
|
||||
const AUTOSELECT_DRIVE_COUNT = 1
|
||||
const numberOfDrives = action.data.length
|
||||
const numberOfDrives = drives.length
|
||||
if (numberOfDrives === AUTOSELECT_DRIVE_COUNT) {
|
||||
const drive = _.first(action.data)
|
||||
const [ drive ] = drives
|
||||
|
||||
// Even if there's no image selected, we need to call several
|
||||
// drive/image related checks, and `{}` works fine with them
|
||||
@ -198,27 +197,42 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
|
||||
!constraints.isSystemDrive(drive)
|
||||
|
||||
])) {
|
||||
// Auto-select this drive
|
||||
return storeReducer(newState, {
|
||||
type: ACTIONS.SELECT_DRIVE,
|
||||
data: drive.device
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const selectedDevice = newState.getIn([ 'selection', 'drive' ])
|
||||
|
||||
if (selectedDevice && !_.find(action.data, {
|
||||
device: selectedDevice
|
||||
})) {
|
||||
// Deselect this drive in case it still is selected
|
||||
return storeReducer(newState, {
|
||||
type: ACTIONS.DESELECT_DRIVE
|
||||
type: ACTIONS.DESELECT_DRIVE,
|
||||
data: drive.device
|
||||
})
|
||||
}
|
||||
|
||||
return newState
|
||||
const selectedDevices = newState.getIn([ 'selection', 'devices' ]).toJS()
|
||||
|
||||
// Remove selected drives that are stale, i.e. missing from availableDrives
|
||||
return _.reduce(selectedDevices, (accState, device) => {
|
||||
// Check whether the drive still exists in availableDrives
|
||||
if (device && !_.find(drives, {
|
||||
device
|
||||
})) {
|
||||
// Deselect this drive gone from availableDrives
|
||||
return storeReducer(accState, {
|
||||
type: ACTIONS.DESELECT_DRIVE,
|
||||
data: device
|
||||
})
|
||||
}
|
||||
|
||||
return accState
|
||||
}, newState)
|
||||
}
|
||||
|
||||
case ACTIONS.SET_FLASH_STATE: {
|
||||
// Type: action.data : FlashStateObject
|
||||
|
||||
if (!state.get('isFlashing')) {
|
||||
throw errors.createError({
|
||||
title: 'Can\'t set the flashing state when not flashing'
|
||||
@ -264,6 +278,8 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
|
||||
}
|
||||
|
||||
case ACTIONS.UNSET_FLASHING_FLAG: {
|
||||
// Type: action.data : FlashResultsObject
|
||||
|
||||
if (!action.data) {
|
||||
throw errors.createError({
|
||||
title: 'Missing results'
|
||||
@ -305,40 +321,46 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
|
||||
}
|
||||
|
||||
case ACTIONS.SELECT_DRIVE: {
|
||||
if (!action.data) {
|
||||
// Type: action.data : String
|
||||
|
||||
const device = action.data
|
||||
|
||||
if (!device) {
|
||||
throw errors.createError({
|
||||
title: 'Missing drive'
|
||||
})
|
||||
}
|
||||
|
||||
if (!_.isString(action.data)) {
|
||||
if (!_.isString(device)) {
|
||||
throw errors.createError({
|
||||
title: `Invalid drive: ${action.data}`
|
||||
title: `Invalid drive: ${device}`
|
||||
})
|
||||
}
|
||||
|
||||
const selectedDrive = findDrive(state, action.data)
|
||||
const selectedDrive = _.find(getAvailableDrives(state), { device })
|
||||
|
||||
if (!selectedDrive) {
|
||||
throw errors.createError({
|
||||
title: `The drive is not available: ${action.data}`
|
||||
title: `The drive is not available: ${device}`
|
||||
})
|
||||
}
|
||||
|
||||
if (selectedDrive.get('isReadOnly')) {
|
||||
if (selectedDrive.isReadOnly) {
|
||||
throw errors.createError({
|
||||
title: 'The drive is write-protected'
|
||||
})
|
||||
}
|
||||
|
||||
const image = state.getIn([ 'selection', 'image' ])
|
||||
if (image && !constraints.isDriveLargeEnough(selectedDrive.toJS(), image.toJS())) {
|
||||
if (image && !constraints.isDriveLargeEnough(selectedDrive, image.toJS())) {
|
||||
throw errors.createError({
|
||||
title: 'The drive is not large enough'
|
||||
})
|
||||
}
|
||||
|
||||
return state.setIn([ 'selection', 'drive' ], Immutable.fromJS(action.data))
|
||||
const selectedDevices = state.getIn([ 'selection', 'devices' ])
|
||||
|
||||
return state.setIn([ 'selection', 'devices' ], selectedDevices.add(device))
|
||||
}
|
||||
|
||||
// TODO(jhermsmeier): Consolidate these assertions
|
||||
@ -346,6 +368,8 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
|
||||
// place where all the image extension / format handling
|
||||
// takes place, to avoid having to check 2+ locations with different logic
|
||||
case ACTIONS.SELECT_IMAGE: {
|
||||
// Type: action.data : ImageObject
|
||||
|
||||
verifyNoNilFields(action.data, selectImageNoNilFields, 'image')
|
||||
|
||||
if (!_.isString(action.data.path)) {
|
||||
@ -432,24 +456,41 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
|
||||
})
|
||||
}
|
||||
|
||||
const selectedDrive = findDrive(state, state.getIn([ 'selection', 'drive' ]))
|
||||
const selectedDevices = state.getIn([ 'selection', 'devices' ])
|
||||
|
||||
return _.attempt(() => {
|
||||
if (selectedDrive && !_.every([
|
||||
constraints.isDriveValid(selectedDrive.toJS(), action.data),
|
||||
constraints.isDriveSizeRecommended(selectedDrive.toJS(), action.data)
|
||||
])) {
|
||||
return storeReducer(state, {
|
||||
type: ACTIONS.DESELECT_DRIVE
|
||||
// Remove image-incompatible drives from selection with `constraints.isDriveValid`
|
||||
return _.reduce(selectedDevices.toJS(), (accState, device) => {
|
||||
const drive = _.find(getAvailableDrives(state), { device })
|
||||
if (!constraints.isDriveValid(drive, action.data) || !constraints.isDriveSizeRecommended(drive, action.data)) {
|
||||
return storeReducer(accState, {
|
||||
type: ACTIONS.DESELECT_DRIVE,
|
||||
data: device
|
||||
})
|
||||
}
|
||||
|
||||
return state
|
||||
}).setIn([ 'selection', 'image' ], Immutable.fromJS(action.data))
|
||||
return accState
|
||||
}, state).setIn([ 'selection', 'image' ], Immutable.fromJS(action.data))
|
||||
}
|
||||
|
||||
case ACTIONS.DESELECT_DRIVE: {
|
||||
return state.deleteIn([ 'selection', 'drive' ])
|
||||
// Type: action.data : String
|
||||
|
||||
if (!action.data) {
|
||||
throw errors.createError({
|
||||
title: 'Missing drive'
|
||||
})
|
||||
}
|
||||
|
||||
if (!_.isString(action.data)) {
|
||||
throw errors.createError({
|
||||
title: `Invalid drive: ${action.data}`
|
||||
})
|
||||
}
|
||||
|
||||
const selectedDevices = state.getIn([ 'selection', 'devices' ])
|
||||
|
||||
// Remove drive from set in state
|
||||
return state.setIn([ 'selection', 'devices' ], selectedDevices.delete(action.data))
|
||||
}
|
||||
|
||||
case ACTIONS.DESELECT_IMAGE: {
|
||||
@ -457,6 +498,8 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
|
||||
}
|
||||
|
||||
case ACTIONS.SET_SETTINGS: {
|
||||
// Type: action.data : SettingsObject
|
||||
|
||||
if (!action.data) {
|
||||
throw errors.createError({
|
||||
title: 'Missing settings'
|
||||
|
@ -143,8 +143,7 @@ describe('Model: availableDrives', function () {
|
||||
|
||||
describe('given no selected image and no selected drive', function () {
|
||||
beforeEach(function () {
|
||||
selectionState.deselectDrive()
|
||||
selectionState.deselectImage()
|
||||
selectionState.clear()
|
||||
})
|
||||
|
||||
it('should auto-select a single valid available drive', function () {
|
||||
@ -164,7 +163,7 @@ describe('Model: availableDrives', function () {
|
||||
])
|
||||
|
||||
m.chai.expect(selectionState.hasDrive()).to.be.true
|
||||
m.chai.expect(selectionState.getDrive().device).to.equal('/dev/sdb')
|
||||
m.chai.expect(selectionState.getCurrentDrive().device).to.equal('/dev/sdb')
|
||||
})
|
||||
})
|
||||
|
||||
@ -176,7 +175,7 @@ describe('Model: availableDrives', function () {
|
||||
this.imagePath = '/mnt/bar/foo.img'
|
||||
}
|
||||
|
||||
selectionState.deselectDrive()
|
||||
selectionState.clear()
|
||||
selectionState.selectImage({
|
||||
path: this.imagePath,
|
||||
extension: 'img',
|
||||
@ -240,7 +239,7 @@ describe('Model: availableDrives', function () {
|
||||
}
|
||||
])
|
||||
|
||||
m.chai.expect(selectionState.getDrive()).to.deep.equal({
|
||||
m.chai.expect(selectionState.getCurrentDrive()).to.deep.equal({
|
||||
device: '/dev/sdb',
|
||||
name: 'Foo',
|
||||
size: 2000000000,
|
||||
@ -420,7 +419,7 @@ describe('Model: availableDrives', function () {
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
selectionState.deselectDrive()
|
||||
selectionState.clear()
|
||||
})
|
||||
|
||||
it('should be deleted if its not contained in the available drives anymore', function () {
|
||||
|
@ -28,8 +28,8 @@ describe('Model: selectionState', function () {
|
||||
selectionState.clear()
|
||||
})
|
||||
|
||||
it('getDrive() should return undefined', function () {
|
||||
const drive = selectionState.getDrive()
|
||||
it('getCurrentDrive() should return undefined', function () {
|
||||
const drive = selectionState.getCurrentDrive()
|
||||
m.chai.expect(drive).to.be.undefined
|
||||
})
|
||||
|
||||
@ -96,9 +96,9 @@ describe('Model: selectionState', function () {
|
||||
selectionState.selectDrive('/dev/disk2')
|
||||
})
|
||||
|
||||
describe('.getDrive()', function () {
|
||||
describe('.getCurrentDrive()', function () {
|
||||
it('should return the drive', function () {
|
||||
const drive = selectionState.getDrive()
|
||||
const drive = selectionState.getCurrentDrive()
|
||||
m.chai.expect(drive).to.deep.equal({
|
||||
device: '/dev/disk2',
|
||||
name: 'USB Drive',
|
||||
@ -115,11 +115,13 @@ describe('Model: selectionState', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('.setDrive()', function () {
|
||||
it('should override the drive', function () {
|
||||
describe('.selectDrive()', function () {
|
||||
it('should queue the drive', function () {
|
||||
selectionState.selectDrive('/dev/disk5')
|
||||
const drive = selectionState.getDrive()
|
||||
m.chai.expect(drive).to.deep.equal({
|
||||
const drives = selectionState.getSelectedDevices()
|
||||
const lastDriveDevice = _.last(drives)
|
||||
const lastDrive = _.find(availableDrives.getDrives(), { device: lastDriveDevice })
|
||||
m.chai.expect(lastDrive).to.deep.equal({
|
||||
device: '/dev/disk5',
|
||||
name: 'USB Drive',
|
||||
size: 999999999,
|
||||
@ -128,17 +130,117 @@ describe('Model: selectionState', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('.removeDrive()', function () {
|
||||
it('should clear the drive', function () {
|
||||
selectionState.deselectDrive()
|
||||
const drive = selectionState.getDrive()
|
||||
describe('.deselectDrive()', function () {
|
||||
it('should clear drives', function () {
|
||||
const firstDrive = selectionState.getCurrentDrive()
|
||||
selectionState.deselectDrive(firstDrive.device)
|
||||
const secondDrive = selectionState.getCurrentDrive()
|
||||
selectionState.deselectDrive(secondDrive.device)
|
||||
const drive = selectionState.getCurrentDrive()
|
||||
m.chai.expect(drive).to.be.undefined
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('given several drives', function () {
|
||||
beforeEach(function () {
|
||||
this.drives = [
|
||||
{
|
||||
device: '/dev/sdb',
|
||||
description: 'DataTraveler 2.0',
|
||||
size: 999999999,
|
||||
mountpoint: '/media/UNTITLED',
|
||||
name: '/dev/sdb',
|
||||
system: false,
|
||||
isReadOnly: false
|
||||
},
|
||||
{
|
||||
device: '/dev/disk2',
|
||||
name: 'USB Drive 2',
|
||||
size: 999999999,
|
||||
isReadOnly: false
|
||||
},
|
||||
{
|
||||
device: '/dev/disk3',
|
||||
name: 'USB Drive 3',
|
||||
size: 999999999,
|
||||
isReadOnly: false
|
||||
}
|
||||
]
|
||||
|
||||
availableDrives.setDrives(this.drives)
|
||||
|
||||
selectionState.selectDrive(this.drives[0].device)
|
||||
selectionState.selectDrive(this.drives[1].device)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
selectionState.clear()
|
||||
availableDrives.setDrives([])
|
||||
})
|
||||
|
||||
it('should be able to add more drives', function () {
|
||||
selectionState.selectDrive(this.drives[2].device)
|
||||
m.chai.expect(selectionState.getSelectedDevices()).to.deep.equal(_.map(this.drives, 'device'))
|
||||
})
|
||||
|
||||
it('should be able to remove drives', function () {
|
||||
selectionState.deselectDrive(this.drives[1].device)
|
||||
m.chai.expect(selectionState.getSelectedDevices()).to.deep.equal([ this.drives[0].device ])
|
||||
})
|
||||
|
||||
it('current drive should be affected by add order', function () {
|
||||
m.chai.expect(selectionState.getCurrentDrive()).to.deep.equal(this.drives[0])
|
||||
selectionState.toggleDrive(this.drives[0].device)
|
||||
selectionState.toggleDrive(this.drives[0].device)
|
||||
m.chai.expect(selectionState.getCurrentDrive()).to.deep.equal(this.drives[1])
|
||||
})
|
||||
|
||||
it('should keep system drives selected', function () {
|
||||
const systemDrive = {
|
||||
device: '/dev/disk0',
|
||||
name: 'USB Drive 0',
|
||||
size: 999999999,
|
||||
isReadOnly: false,
|
||||
system: true
|
||||
}
|
||||
|
||||
const newDrives = [ ..._.initial(this.drives), systemDrive ]
|
||||
availableDrives.setDrives(newDrives)
|
||||
|
||||
selectionState.selectDrive(systemDrive.device)
|
||||
availableDrives.setDrives(newDrives)
|
||||
m.chai.expect(selectionState.getSelectedDevices()).to.deep.equal(_.map(newDrives, 'device'))
|
||||
})
|
||||
|
||||
it('should be able to remove a drive', function () {
|
||||
m.chai.expect(selectionState.getSelectedDevices().length).to.equal(2)
|
||||
selectionState.toggleDrive(this.drives[0].device)
|
||||
m.chai.expect(selectionState.getSelectedDevices()).to.deep.equal([ this.drives[1].device ])
|
||||
})
|
||||
|
||||
describe('.deselectAllDrives()', function () {
|
||||
it('should remove all drives', function () {
|
||||
selectionState.deselectAllDrives()
|
||||
m.chai.expect(selectionState.getSelectedDevices()).to.deep.equal([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('.deselectOtherDrives()', function () {
|
||||
it('should deselect other drives', function () {
|
||||
selectionState.deselectOtherDrives(this.drives[0].device)
|
||||
m.chai.expect(selectionState.getSelectedDevices()).to.not.include.members([ this.drives[1].device ])
|
||||
})
|
||||
|
||||
it('should not remove the specified drive', function () {
|
||||
selectionState.deselectOtherDrives(this.drives[0].device)
|
||||
m.chai.expect(selectionState.getSelectedDevices()).to.deep.equal([ this.drives[0].device ])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('given no drive', function () {
|
||||
describe('.setDrive()', function () {
|
||||
describe('.selectDrive()', function () {
|
||||
it('should be able to set a drive', function () {
|
||||
availableDrives.setDrives([
|
||||
{
|
||||
@ -150,7 +252,7 @@ describe('Model: selectionState', function () {
|
||||
])
|
||||
|
||||
selectionState.selectDrive('/dev/disk5')
|
||||
const drive = selectionState.getDrive()
|
||||
const drive = selectionState.getCurrentDrive()
|
||||
m.chai.expect(drive).to.deep.equal({
|
||||
device: '/dev/disk5',
|
||||
name: 'USB Drive',
|
||||
@ -159,7 +261,7 @@ describe('Model: selectionState', function () {
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw if drive is write protected', function () {
|
||||
it('should throw if drive is read-only', function () {
|
||||
availableDrives.setDrives([
|
||||
{
|
||||
device: '/dev/disk1',
|
||||
@ -219,7 +321,7 @@ describe('Model: selectionState', function () {
|
||||
selectionState.selectImage(this.image)
|
||||
})
|
||||
|
||||
describe('.setDrive()', function () {
|
||||
describe('.selectDrive()', function () {
|
||||
it('should throw if drive is not large enough', function () {
|
||||
availableDrives.setDrives([
|
||||
{
|
||||
@ -298,7 +400,7 @@ describe('Model: selectionState', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('.setImage()', function () {
|
||||
describe('.selectImage()', function () {
|
||||
it('should override the image', function () {
|
||||
selectionState.selectImage({
|
||||
path: 'bar.img',
|
||||
@ -319,7 +421,7 @@ describe('Model: selectionState', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('.removeImage()', function () {
|
||||
describe('.deselectImage()', function () {
|
||||
it('should clear the image', function () {
|
||||
selectionState.deselectImage()
|
||||
|
||||
@ -332,7 +434,9 @@ describe('Model: selectionState', function () {
|
||||
})
|
||||
|
||||
describe('given no image', function () {
|
||||
describe('.setImage()', function () {
|
||||
describe('.selectImage()', function () {
|
||||
afterEach(selectionState.clear)
|
||||
|
||||
it('should be able to set an image', function () {
|
||||
selectionState.selectImage({
|
||||
path: 'foo.img',
|
||||
@ -755,7 +859,7 @@ describe('Model: selectionState', function () {
|
||||
{
|
||||
device: '/dev/disk1',
|
||||
name: 'USB Drive',
|
||||
size: 999999999,
|
||||
size: 123456789,
|
||||
isReadOnly: false
|
||||
}
|
||||
])
|
||||
@ -767,10 +871,10 @@ describe('Model: selectionState', function () {
|
||||
path: 'foo.img',
|
||||
extension: 'img',
|
||||
size: {
|
||||
original: 9999999999,
|
||||
original: 1234567890,
|
||||
final: {
|
||||
estimation: false,
|
||||
value: 9999999999
|
||||
value: 1234567890
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -890,20 +994,126 @@ describe('Model: selectionState', function () {
|
||||
m.chai.expect(selectionState.hasImage()).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe('.deselectImage()', function () {
|
||||
it('should not clear any drives', function () {
|
||||
beforeEach(function () {
|
||||
selectionState.deselectImage()
|
||||
})
|
||||
|
||||
it('getCurrentDrive() should return the selected drive object', function () {
|
||||
const drive = selectionState.getCurrentDrive()
|
||||
m.chai.expect(drive).to.deep.equal({
|
||||
device: '/dev/disk1',
|
||||
isReadOnly: false,
|
||||
name: 'USB Drive',
|
||||
size: 999999999
|
||||
})
|
||||
})
|
||||
|
||||
it('getImagePath() should return undefined', function () {
|
||||
const imagePath = selectionState.getImagePath()
|
||||
m.chai.expect(imagePath).to.be.undefined
|
||||
})
|
||||
|
||||
it('getImageSize() should return undefined', function () {
|
||||
const imageSize = selectionState.getImageSize()
|
||||
m.chai.expect(imageSize).to.be.undefined
|
||||
})
|
||||
|
||||
it('should not clear any drives', function () {
|
||||
m.chai.expect(selectionState.hasDrive()).to.be.true
|
||||
})
|
||||
|
||||
it('hasImage() should return false', function () {
|
||||
const hasImage = selectionState.hasImage()
|
||||
m.chai.expect(hasImage).to.be.false
|
||||
})
|
||||
})
|
||||
describe('.deselectDrive()', function () {
|
||||
|
||||
describe('.deselectAllDrives()', function () {
|
||||
beforeEach(function () {
|
||||
selectionState.deselectAllDrives()
|
||||
})
|
||||
|
||||
it('getCurrentDrive() should return undefined', function () {
|
||||
const drive = selectionState.getCurrentDrive()
|
||||
m.chai.expect(drive).to.be.undefined
|
||||
})
|
||||
|
||||
it('getImagePath() should return the image path', function () {
|
||||
const imagePath = selectionState.getImagePath()
|
||||
m.chai.expect(imagePath).to.equal('foo.img')
|
||||
})
|
||||
|
||||
it('getImageSize() should return the image size', function () {
|
||||
const imageSize = selectionState.getImageSize()
|
||||
m.chai.expect(imageSize).to.equal(999999999)
|
||||
})
|
||||
|
||||
it('hasDrive() should return false', function () {
|
||||
const hasDrive = selectionState.hasDrive()
|
||||
m.chai.expect(hasDrive).to.be.false
|
||||
})
|
||||
|
||||
it('should not clear the image', function () {
|
||||
selectionState.deselectDrive()
|
||||
m.chai.expect(selectionState.hasImage()).to.be.true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('given several drives', function () {
|
||||
beforeEach(function () {
|
||||
availableDrives.setDrives([
|
||||
{
|
||||
device: '/dev/disk1',
|
||||
name: 'USB Drive 1',
|
||||
size: 999999999,
|
||||
isReadOnly: false
|
||||
},
|
||||
{
|
||||
device: '/dev/disk2',
|
||||
name: 'USB Drive 2',
|
||||
size: 999999999,
|
||||
isReadOnly: false
|
||||
},
|
||||
{
|
||||
device: '/dev/disk3',
|
||||
name: 'USB Drive 3',
|
||||
size: 999999999,
|
||||
isReadOnly: false
|
||||
}
|
||||
])
|
||||
|
||||
selectionState.selectDrive('/dev/disk1')
|
||||
selectionState.selectDrive('/dev/disk2')
|
||||
selectionState.selectDrive('/dev/disk3')
|
||||
|
||||
selectionState.selectImage({
|
||||
path: 'foo.img',
|
||||
extension: 'img',
|
||||
size: {
|
||||
original: 999999999,
|
||||
final: {
|
||||
estimation: false,
|
||||
value: 999999999
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('.clear()', function () {
|
||||
it('should clear all selections', function () {
|
||||
m.chai.expect(selectionState.hasDrive()).to.be.true
|
||||
m.chai.expect(selectionState.hasImage()).to.be.true
|
||||
|
||||
selectionState.clear()
|
||||
|
||||
m.chai.expect(selectionState.hasDrive()).to.be.false
|
||||
m.chai.expect(selectionState.hasImage()).to.be.false
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isCurrentDrive()', function () {
|
||||
describe('given a selected drive', function () {
|
||||
beforeEach(function () {
|
||||
@ -924,6 +1134,11 @@ describe('Model: selectionState', function () {
|
||||
selectionState.selectDrive('/dev/sdb')
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
selectionState.clear()
|
||||
availableDrives.setDrives([])
|
||||
})
|
||||
|
||||
it('should return false if an undefined value is passed', function () {
|
||||
m.chai.expect(selectionState.isCurrentDrive()).to.be.false
|
||||
})
|
||||
@ -939,7 +1154,7 @@ describe('Model: selectionState', function () {
|
||||
|
||||
describe('given no selected drive', function () {
|
||||
beforeEach(function () {
|
||||
selectionState.deselectDrive()
|
||||
selectionState.clear()
|
||||
})
|
||||
|
||||
it('should return false if an undefined value is passed', function () {
|
||||
@ -952,7 +1167,7 @@ describe('Model: selectionState', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('.toggleSetDrive()', function () {
|
||||
describe('.toggleDrive()', function () {
|
||||
describe('given a selected drive', function () {
|
||||
beforeEach(function () {
|
||||
this.drive = {
|
||||
@ -971,7 +1186,7 @@ describe('Model: selectionState', function () {
|
||||
this.drive,
|
||||
{
|
||||
device: '/dev/disk2',
|
||||
name: 'USB Drive',
|
||||
name: 'USB Drive 2',
|
||||
size: 999999999,
|
||||
isReadOnly: false
|
||||
}
|
||||
@ -980,13 +1195,18 @@ describe('Model: selectionState', function () {
|
||||
selectionState.selectDrive(this.drive.device)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
selectionState.clear()
|
||||
availableDrives.setDrives([])
|
||||
})
|
||||
|
||||
it('should be able to remove the drive', function () {
|
||||
m.chai.expect(selectionState.hasDrive()).to.be.true
|
||||
selectionState.toggleDrive(this.drive.device)
|
||||
m.chai.expect(selectionState.hasDrive()).to.be.false
|
||||
})
|
||||
|
||||
it('should be able to replace the drive', function () {
|
||||
it('should not replace a different drive', function () {
|
||||
const drive = {
|
||||
device: '/dev/disk2',
|
||||
name: 'USB Drive',
|
||||
@ -994,29 +1214,48 @@ describe('Model: selectionState', function () {
|
||||
isReadOnly: false
|
||||
}
|
||||
|
||||
m.chai.expect(selectionState.getDrive()).to.deep.equal(this.drive)
|
||||
m.chai.expect(selectionState.getCurrentDrive()).to.deep.equal(this.drive)
|
||||
selectionState.toggleDrive(drive.device)
|
||||
m.chai.expect(selectionState.getDrive()).to.deep.equal(drive)
|
||||
m.chai.expect(selectionState.getDrive()).to.not.deep.equal(this.drive)
|
||||
m.chai.expect(selectionState.getCurrentDrive()).to.deep.equal(this.drive)
|
||||
m.chai.expect(selectionState.getCurrentDrive()).to.not.deep.equal(drive)
|
||||
})
|
||||
})
|
||||
|
||||
describe('given no selected drive', function () {
|
||||
beforeEach(function () {
|
||||
selectionState.deselectDrive()
|
||||
selectionState.clear()
|
||||
|
||||
availableDrives.setDrives([
|
||||
{
|
||||
device: '/dev/disk2',
|
||||
name: 'USB Drive 2',
|
||||
size: 999999999,
|
||||
isReadOnly: false
|
||||
},
|
||||
{
|
||||
device: '/dev/disk3',
|
||||
name: 'USB Drive 3',
|
||||
size: 999999999,
|
||||
isReadOnly: false
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
availableDrives.setDrives([])
|
||||
})
|
||||
|
||||
it('should set the drive', function () {
|
||||
const drive = {
|
||||
device: '/dev/disk2',
|
||||
name: 'USB Drive',
|
||||
name: 'USB Drive 2',
|
||||
size: 999999999,
|
||||
isReadOnly: false
|
||||
}
|
||||
|
||||
m.chai.expect(selectionState.hasDrive()).to.be.false
|
||||
selectionState.toggleDrive(drive.device)
|
||||
m.chai.expect(selectionState.getDrive()).to.deep.equal(drive)
|
||||
m.chai.expect(selectionState.getCurrentDrive()).to.deep.equal(drive)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user