diff --git a/lib/gui/app/components/drive-selector/controllers/drive-selector.js b/lib/gui/app/components/drive-selector/controllers/drive-selector.js
index 8b1141ec..f61acc5d 100644
--- a/lib/gui/app/components/drive-selector/controllers/drive-selector.js
+++ b/lib/gui/app/components/drive-selector/controllers/drive-selector.js
@@ -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
diff --git a/lib/gui/app/components/drive-selector/templates/drive-selector-modal.tpl.html b/lib/gui/app/components/drive-selector/templates/drive-selector-modal.tpl.html
index 2fca7178..084f5a23 100644
--- a/lib/gui/app/components/drive-selector/templates/drive-selector-modal.tpl.html
+++ b/lib/gui/app/components/drive-selector/templates/drive-selector-modal.tpl.html
@@ -38,7 +38,7 @@
+ ng-disabled="!modal.state.isDriveSelected(drive.device)">
diff --git a/lib/gui/app/pages/finish/controllers/finish.js b/lib/gui/app/pages/finish/controllers/finish.js
index 7e7e5db5..a95fcb90 100644
--- a/lib/gui/app/pages/finish/controllers/finish.js
+++ b/lib/gui/app/pages/finish/controllers/finish.js
@@ -51,7 +51,7 @@ module.exports = function ($state) {
if (!options.preserveImage) {
selectionState.deselectImage()
}
- selectionState.deselectDrive()
+ selectionState.deselectAllDrives()
analytics.logEvent('Restart', options)
$state.go('main')
}
diff --git a/lib/gui/app/pages/main/templates/main.tpl.html b/lib/gui/app/pages/main/templates/main.tpl.html
index 99cc5474..dc6ba64b 100644
--- a/lib/gui/app/pages/main/templates/main.tpl.html
+++ b/lib/gui/app/pages/main/templates/main.tpl.html
@@ -66,11 +66,11 @@
'text-disabled': main.shouldDriveStepBeDisabled()
}">
+ uib-tooltip="{{ main.selection.getCurrentDrive().description }} ({{ main.selection.getCurrentDrive().displayName }})">
- {{ (main.selection.getDrive().description || "") | middleEllipses:11 }}
+ {{ (main.selection.getCurrentDrive().description || "") | middleEllipses:11 }}
- {{ main.selection.getDrive().size | closestUnit }}
+ {{ main.selection.getCurrentDrive().size | closestUnit }}
@@ -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()">
diff --git a/lib/shared/models/selection-state.js b/lib/shared/models/selection-state.js
index 6df9d126..f6d3c65c 100644
--- a/lib/shared/models/selection-state.js
+++ b/lib/shared/models/selection-state.js
@@ -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)
}
diff --git a/lib/shared/store.js b/lib/shared/store.js
index fdc5265f..34738313 100644
--- a/lib/shared/store.js
+++ b/lib/shared/store.js
@@ -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
+
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'
diff --git a/tests/shared/models/available-drives.spec.js b/tests/shared/models/available-drives.spec.js
index f0d9a2c4..347af410 100644
--- a/tests/shared/models/available-drives.spec.js
+++ b/tests/shared/models/available-drives.spec.js
@@ -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 () {
diff --git a/tests/shared/models/selection-state.spec.js b/tests/shared/models/selection-state.spec.js
index a6faecf9..56b7cc95 100644
--- a/tests/shared/models/selection-state.spec.js
+++ b/tests/shared/models/selection-state.spec.js
@@ -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)
})
})
})