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:
Benedict Aas 2018-02-23 17:45:49 +00:00 committed by GitHub
parent ee93013220
commit 207c2ef5b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 482 additions and 114 deletions

View File

@ -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

View File

@ -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()">

View File

@ -51,7 +51,7 @@ module.exports = function ($state) {
if (!options.preserveImage) {
selectionState.deselectImage()
}
selectionState.deselectDrive()
selectionState.deselectAllDrives()
analytics.logEvent('Restart', options)
$state.go('main')
}

View File

@ -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>

View File

@ -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)
}

View File

@ -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'

View File

@ -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 () {

View File

@ -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)
})
})
})