refactor: multi-writes preparatory changes (#2124)

We add some preparatory changes including new utility functions, as well
as changes throughout the codebase that reflect the change from single
drives to a list of drives, given multi-writes is coming.

Change-Type: patch
Changelog-Entry: Various preparatory changes to account for
multi-writes.
This commit is contained in:
Benedict Aas 2018-03-23 14:36:39 +00:00 committed by GitHub
parent 8bd9ff58c4
commit 4140d49db3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 649 additions and 81 deletions

View File

@ -119,7 +119,6 @@ module.exports = function (
previouslySelected: selectionState.isCurrentDrive(drive.device)
})
selectionState.deselectOtherDrives(drive.device)
selectionState.toggleDrive(drive.device)
}
})

View File

@ -29,7 +29,7 @@ const utils = require('../../../shared/utils')
* @example
* const status = progressStatus.fromFlashState({
* flashing: 1,
* validating: 0,
* verifying: 0,
* succeeded: 0,
* failed: 0,
* percentage: 55,
@ -41,7 +41,7 @@ const utils = require('../../../shared/utils')
*/
exports.fromFlashState = (state) => {
const isFlashing = Boolean(state.flashing)
const isValidating = !isFlashing && Boolean(state.validating)
const isValidating = !isFlashing && Boolean(state.verifying)
const shouldValidate = settings.get('validateWriteOnSuccess')
const shouldUnmount = settings.get('unmountOnSuccess')

View File

@ -16,12 +16,52 @@
'use strict'
const _ = require('lodash')
const settings = require('../../../models/settings')
const selectionState = require('../../../../../shared/models/selection-state')
const analytics = require('../../../modules/analytics')
const exceptionReporter = require('../../../modules/exception-reporter')
module.exports = function (DriveSelectorService) {
/**
* @summary Get drive title based on device quantity
* @function
* @public
*
* @returns {String} - drives title
*
* @example
* console.log(DriveSelectionController.getDrivesTitle())
* > 'Multiple Drives (4)'
*/
this.getDrivesTitle = () => {
const drives = selectionState.getSelectedDrives()
// eslint-disable-next-line no-magic-numbers
if (drives.length === 1) {
return _.head(drives).description
}
return `Multiple Devices (${drives.length})`
}
/**
* @summary Get drive list label
* @function
* @public
*
* @returns {String} - 'list' of drives separated by newlines
*
* @example
* console.log(DriveSelectionController.getDriveListLabel())
* > 'My Drive (/dev/disk1)\nMy Other Drive (/dev/disk2)'
*/
this.getDriveListLabel = () => {
return _.join(_.map(selectionState.getSelectedDrives(), (drive) => {
return `${drive.description} (${drive.displayName})`
}), '\n')
}
/**
* @summary Open drive selector
* @function

View File

@ -32,12 +32,12 @@ module.exports = function (
FlashErrorModalService
) {
/**
* @summary Flash image to a drive
* @summary Flash image to drives
* @function
* @public
*
* @param {Object} image - image
* @param {Object} drive - drive
* @param {Array<Object>} drives - drives
*
* @example
* FlashController.flashImageToDrive({
@ -57,7 +57,7 @@ module.exports = function (
* system: false
* })
*/
this.flashImageToDrive = (image, drive) => {
this.flashImageToDrive = (image, drives) => {
if (flashState.isFlashing()) {
return
}
@ -72,17 +72,17 @@ module.exports = function (
const iconPath = '../../../assets/icon.png'
imageWriter.flash(image.path, [ drive.device ]).then(() => {
imageWriter.flash(image.path, drives).then(() => {
if (!flashState.wasLastFlashCancelled()) {
notification.send('Success!', {
body: messages.info.flashComplete(path.basename(image.path), drive),
body: messages.info.flashComplete(path.basename(image.path), drives),
icon: iconPath
})
$state.go('success')
}
}).catch((error) => {
notification.send('Oops! Looks like the flash failed.', {
body: messages.error.flashFailure(path.basename(image.path), drive),
body: messages.error.flashFailure(path.basename(image.path), drives),
icon: iconPath
})

View File

@ -129,7 +129,7 @@ svg-icon > img[disabled] {
&.target-status-flashing > .target-status-dot {
background-color: $palette-theme-warning-background;
}
&.target-status-validating > .target-status-dot {
&.target-status-verifying > .target-status-dot {
background-color: $palette-theme-primary-background;
}
&.target-status-succeeded > .target-status-dot {
@ -149,3 +149,7 @@ svg-icon > img[disabled] {
color: gray;
}
}
.tooltip-inner {
white-space: pre-line;
}

View File

@ -66,14 +66,14 @@
'text-disabled': main.shouldDriveStepBeDisabled()
}">
<span class="drive-step step-name"
uib-tooltip="{{ main.selection.getCurrentDrive().description }} ({{ main.selection.getCurrentDrive().displayName }})">
uib-tooltip="{{ drive.getDriveListLabel() }}">
<!-- middleEllipses errors on undefined, therefore fallback to empty string -->
{{ (main.selection.getCurrentDrive().description || "") | middleEllipses:11 }}
{{ drive.getDrivesTitle() | middleEllipses:20 }}
</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>
uib-tooltip="{{ main.constraints.getListDriveImageCompatibilityStatuses(main.selection.getSelectedDrives(), main.selection.getImage())[0].message }}"
ng-show="main.constraints.hasListDriveImageCompatibilityStatus(main.selection.getSelectedDrives(), main.selection.getImage())"></span>
</div>
<button class="button button-link step-footer"
tabindex="{{ main.selection.hasDrive() ? 2 : -1 }}"
@ -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.getCurrentDrive())"
ng-click="flash.flashImageToDrive(main.selection.getImage(), main.selection.getSelectedDevices())"
ng-disabled="main.shouldFlashStepBeDisabled() || main.state.getLastFlashErrorCode()">
<span ng-bind="flash.getProgressButtonLabel()"></span>
</progress-button>

View File

@ -6587,7 +6587,7 @@ svg-icon > img[disabled] {
margin-right: 5px; }
.target-status-line.target-status-flashing > .target-status-dot {
background-color: #ff912f; }
.target-status-line.target-status-validating > .target-status-dot {
.target-status-line.target-status-verifying > .target-status-dot {
background-color: #5793db; }
.target-status-line.target-status-succeeded > .target-status-dot {
background-color: #5fb835; }
@ -6600,6 +6600,9 @@ svg-icon > img[disabled] {
.target-status-line > .target-status-message {
color: gray; }
.tooltip-inner {
white-space: pre-line; }
/*
* Copyright 2016 resin.io
*

View File

@ -404,6 +404,61 @@ exports.getDriveImageCompatibilityStatuses = (drive, image) => {
return statusList
}
/**
* @summary Get drive/image compatibility status for many drives
* @function
* @public
*
* @description
* Given an image and a list of drives, return all compatibility status objects,
* containing the status type (ERROR, WARNING), and accompanying status message.
*
* @param {Object[]} drives - drives
* @param {Object} image - image
* @returns {Object[]} list of compatibility status objects
*
* @example
* const drives = [
* {
* device: '/dev/disk2',
* name: 'My Drive',
* size: 4000000000
* },
* {
* device: '/dev/disk1',
* name: 'My Other Drive',
* size: 780000000
* }
* ]
*
* const image = {
* path: '/path/to/rpi.img',
* size: {
* original: 2000000000,
* final: {
* estimation: false,
* value: 2000000000
* }
* },
* recommendedDriveSize: 4000000000
* })
*
* const statuses = constraints.getListDriveImageCompatibilityStatuses(drives, image)
*
* for ({ type, message } of statuses) {
* if (type === constraints.COMPATIBILITY_STATUS_TYPES.WARNING) {
* // do something
* } else if (type === constraints.COMPATIBILITY_STATUS_TYPES.ERROR) {
* // do something else
* }
* }
*/
exports.getListDriveImageCompatibilityStatuses = (drives, image) => {
return _.flatMap(drives, (drive) => {
return exports.getDriveImageCompatibilityStatuses(drive, image)
})
}
/**
* @summary Does the drive/image pair have at least one compatibility status?
* @function
@ -424,3 +479,24 @@ exports.getDriveImageCompatibilityStatuses = (drive, image) => {
exports.hasDriveImageCompatibilityStatus = (drive, image) => {
return Boolean(exports.getDriveImageCompatibilityStatuses(drive, image).length)
}
/**
* @summary Does any drive/image pair have at least one compatibility status?
* @function
* @public
*
* @description
* Given an image and a drive, return whether they have a connected compatibility status object.
*
* @param {Object[]} drives - drives
* @param {Object} image - image
* @returns {Boolean}
*
* @example
* if (constraints.hasDriveImageCompatibilityStatus(drive, image)) {
* console.log('This drive-image pair has a compatibility status message!')
* }
*/
exports.hasListDriveImageCompatibilityStatus = (drives, image) => {
return Boolean(exports.getListDriveImageCompatibilityStatuses(drives, image).length)
}

View File

@ -36,7 +36,7 @@ module.exports = {
return 'Flashing device(s)'
},
validating: () => {
verifying: () => {
return 'Validating device(s)'
},
@ -56,10 +56,13 @@ module.exports = {
*/
info: {
flashComplete: (imageBasename, drive) => {
flashComplete: (imageBasename, drives) => {
return [
`${imageBasename} was successfully written to`,
`${drive.description} (${drive.displayName})`
// eslint-disable-next-line lodash/prefer-lodash-method
drives.map((drive) => {
return `${drive.description} (${drive.displayName})`
}).join(', ')
].join(' ')
}
@ -189,10 +192,13 @@ module.exports = {
return 'This should should be run with root/administrator permissions.'
},
flashFailure: (imageBasename, drive) => {
flashFailure: (imageBasename, drives) => {
return [
`Something went wrong while writing ${imageBasename}`,
`to ${drive.description} (${drive.displayName})`
`Something went wrong while writing ${imageBasename} to`,
// eslint-disable-next-line lodash/prefer-lodash-method
drives.map((drive) => {
return `${drive.description} (${drive.displayName})`
}).join(', ')
].join(' ')
},

View File

@ -114,34 +114,36 @@ exports.unsetFlashingFlag = (results) => {
* });
*/
exports.setProgressState = (state) => {
if (!_.isString(state.type)) {
throw new Error(`Invalid state type: ${state.type}`)
}
const {
flashing,
verifying,
succeeded,
failed,
percentage,
eta,
speed
} = state
const data = {
flashing,
verifying,
succeeded,
failed,
// NOTE(Shou): we can most likely remove `state.type` when multi-writes
// is in proper, thanks to the status quantities.
const isValidating = state.type === 'check'
const type = isValidating ? 'validating' : 'flashing'
const data = _.assign({
flashing: 0,
validating: 0,
succeeded: 0,
failed: 0,
percentage: _.isNumber(state.percentage) && !_.isNaN(state.percentage)
? Math.floor(state.percentage)
: state.percentage,
eta: state.eta,
percentage: _.isNumber(percentage) && !_.isNaN(percentage)
? Math.floor(percentage)
: percentage,
eta,
speed: _.attempt(() => {
if (_.isNumber(state.speed) && !_.isNaN(state.speed)) {
if (_.isNumber(speed) && !_.isNaN(speed)) {
// Preserve only two decimal places
const PRECISION = 2
return _.round(units.bytesToMegabytes(state.speed), PRECISION)
return _.round(units.bytesToMegabytes(speed), PRECISION)
}
return null
})
}, { [type]: 1 })
}
store.dispatch({
type: store.Actions.SET_FLASH_STATE,
@ -180,7 +182,7 @@ exports.getFlashState = () => {
exports.getFlashQuantities = () => {
return _.pick(exports.getFlashState(), [
'flashing',
'validating',
'verifying',
'succeeded',
'failed'
])

View File

@ -127,6 +127,27 @@ exports.getSelectedDevices = () => {
return store.getState().getIn([ 'selection', 'devices' ]).toJS()
}
/**
* @summary Get all selected drive objects
* @function
* @public
*
* @returns {Object[]} selected drive objects
*
* @example
* for (drive of selectionState.getSelectedDrives()) {
* console.log(drive)
* }
* > '{ device: '/dev/disk1', size: 123456789, ... }'
* > '{ device: '/dev/disk2', size: 987654321, ... }'
*/
exports.getSelectedDrives = () => {
const drives = availableDrives.getDrives()
return _.map(exports.getSelectedDevices(), (device) => {
return _.find(drives, { device })
})
}
/**
* @summary Get the head of the list of selected drives
* @function

View File

@ -88,7 +88,7 @@ const DEFAULT_STATE = Immutable.fromJS({
flashResults: {},
flashState: {
flashing: 0,
validating: 0,
verifying: 0,
succeeded: 0,
failed: 0,
percentage: 0,
@ -246,7 +246,7 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
if (_.every(_.pick(action.data, [
'flashing',
'validating',
'verifying',
'succeeded',
'failed'
]), _.identity)) {

View File

@ -9,7 +9,7 @@ describe('Browser: progressStatus', function () {
beforeEach(function () {
this.state = {
flashing: 1,
validating: 0,
verifying: 0,
succeeded: 0,
failed: 0,
percentage: 0,
@ -36,17 +36,17 @@ describe('Browser: progressStatus', function () {
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('Starting...')
})
it('should handle percentage == 0, validating, unmountOnSuccess', function () {
it('should handle percentage == 0, verifying, unmountOnSuccess', function () {
this.state.speed = 0
this.state.flashing = 0
this.state.validating = 1
this.state.verifying = 1
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('Validating...')
})
it('should handle percentage == 0, validating, !unmountOnSuccess', function () {
it('should handle percentage == 0, verifying, !unmountOnSuccess', function () {
this.state.speed = 0
this.state.flashing = 0
this.state.validating = 1
this.state.verifying = 1
settings.set('unmountOnSuccess', false)
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('Validating...')
})
@ -62,16 +62,16 @@ describe('Browser: progressStatus', function () {
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('50% Flashing')
})
it('should handle percentage == 50, validating, unmountOnSuccess', function () {
it('should handle percentage == 50, verifying, unmountOnSuccess', function () {
this.state.flashing = 0
this.state.validating = 1
this.state.verifying = 1
this.state.percentage = 50
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('50% Validating')
})
it('should handle percentage == 50, validating, !unmountOnSuccess', function () {
it('should handle percentage == 50, verifying, !unmountOnSuccess', function () {
this.state.flashing = 0
this.state.validating = 1
this.state.verifying = 1
this.state.percentage = 50
settings.set('unmountOnSuccess', false)
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('50% Validating')
@ -95,16 +95,16 @@ describe('Browser: progressStatus', function () {
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('Finishing...')
})
it('should handle percentage == 100, validating, unmountOnSuccess', function () {
it('should handle percentage == 100, verifying, unmountOnSuccess', function () {
this.state.flashing = 0
this.state.validating = 1
this.state.verifying = 1
this.state.percentage = 100
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('Unmounting...')
})
it('should handle percentage == 100, validatinf, !unmountOnSuccess', function () {
this.state.flashing = 0
this.state.validating = 1
this.state.verifying = 1
this.state.percentage = 100
settings.set('unmountOnSuccess', false)
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('Finishing...')

View File

@ -33,7 +33,7 @@ describe('Browser: WindowProgress', function () {
this.state = {
flashing: 1,
validating: 0,
verifying: 0,
succeeded: 0,
failed: 0,
percentage: 85,
@ -80,9 +80,9 @@ describe('Browser: WindowProgress', function () {
m.chai.expect(this.setTitleSpy).to.have.been.calledWith(' \u2013 85% Flashing')
})
it('should set the validating title', function () {
it('should set the verifying title', function () {
this.state.flashing = 0
this.state.validating = 1
this.state.verifying = 1
windowProgress.set(this.state)
m.chai.expect(this.setTitleSpy).to.have.been.calledWith(' \u2013 85% Validating')
})

View File

@ -241,7 +241,10 @@ describe('Browser: MainPage', function () {
flashState.setFlashingFlag()
flashState.setProgressState({
type: 'write',
flashing: 1,
verifying: 0,
succeeded: 0,
failed: 0,
percentage: 85,
eta: 15,
speed: 1000
@ -251,6 +254,76 @@ describe('Browser: MainPage', function () {
})
})
describe('DriveSelectionController', function () {
let $controller
let DriveSelectionController
const drivePaths = process.platform === 'win32'
? [ 'E:\\', 'F:\\' ]
: [ '/dev/disk1', '/dev/disk2' ]
const drives = [
{
device: drivePaths[0],
description: 'My Drive',
size: 123456789,
displayName: drivePaths[0],
mountpoints: [ drivePaths[0] ],
isSystem: false,
isReadOnly: false
},
{
device: drivePaths[1],
description: 'My Other Drive',
size: 987654321,
displayName: drivePaths[1],
mountpoints: [ drivePaths[1] ],
isSystem: false,
isReadOnly: false
}
]
beforeEach(angular.mock.inject(function (_$controller_) {
$controller = _$controller_
DriveSelectionController = $controller('DriveSelectionController', {
$scope: {}
})
availableDrives.setDrives(drives)
}))
afterEach(() => {
selectionState.clear()
})
describe('.getDrivesTitle()', function () {
it('should return the drive description when there is one drive', function () {
selectionState.selectDrive(drives[0].device)
m.chai.expect(DriveSelectionController.getDrivesTitle()).to.equal(drives[0].description)
})
it('should return a consolidated title with quantity when there are multiple drives', function () {
selectionState.selectDrive(drives[0].device)
selectionState.selectDrive(drives[1].device)
m.chai.expect(DriveSelectionController.getDrivesTitle()).to.equal('Multiple Devices (2)')
})
})
describe('.getDriveListLabel()', function () {
it('should return the drive description and display name when there is one drive', function () {
const label = `${drives[0].description} (${drives[0].displayName})`
selectionState.selectDrive(drives[0].device)
m.chai.expect(DriveSelectionController.getDriveListLabel()).to.equal(label)
})
it('should return drive descriptions and display names of all drives separated by newlines', function () {
const label = `${drives[0].description} (${drives[0].displayName})\n${drives[1].description} (${drives[1].displayName})`
selectionState.selectDrive(drives[0].device)
selectionState.selectDrive(drives[1].device)
m.chai.expect(DriveSelectionController.getDriveListLabel()).to.equal(label)
})
})
})
describe('page template', function () {
let $state

View File

@ -1229,4 +1229,305 @@ describe('Shared: DriveConstraints', function () {
})
})
})
describe('.getListDriveImageCompatibilityStatuses()', function () {
const drivePaths = process.platform === 'win32'
? [ 'E:\\', 'F:\\', 'G:\\', 'H:\\', 'J:\\', 'K:\\' ]
: [ '/dev/disk1', '/dev/disk2', '/dev/disk3', '/dev/disk4', '/dev/disk5', '/dev/disk6' ]
const drives = [
{
device: drivePaths[0],
description: 'My Drive',
size: 123456789,
displayName: drivePaths[0],
mountpoints: [ { path: __dirname } ],
isSystem: false,
isReadOnly: false
},
{
device: drivePaths[1],
description: 'My Other Drive',
size: 123456789,
displayName: drivePaths[1],
mountpoints: [],
isSystem: false,
isReadOnly: true
},
{
device: drivePaths[2],
description: 'My Drive',
size: 1234567,
displayName: drivePaths[2],
mountpoints: [],
isSystem: false,
isReadOnly: false
},
{
device: drivePaths[3],
description: 'My Drive',
size: 123456789,
displayName: drivePaths[3],
mountpoints: [],
isSystem: true,
isReadOnly: false
},
{
device: drivePaths[4],
description: 'My Drive',
size: 64000000001,
displayName: drivePaths[4],
mountpoints: [],
isSystem: false,
isReadOnly: false
},
{
device: drivePaths[5],
description: 'My Drive',
size: 12345678,
displayName: drivePaths[5],
mountpoints: [],
isSystem: false,
isReadOnly: false
},
{
device: drivePaths[6],
description: 'My Drive',
size: 123456789,
displayName: drivePaths[6],
mountpoints: [],
isSystem: false,
isReadOnly: false
}
]
const image = {
path: path.join(__dirname, 'rpi.img'),
size: {
original: drives[2].size + 1,
final: {
estimation: false,
value: drives[2].size + 1
}
},
recommendedDriveSize: drives[5].size + 1
}
describe('given no drives', function () {
it('should return no statuses', function () {
m.chai.expect(constraints.getListDriveImageCompatibilityStatuses([], image)).to.deep.equal([])
})
})
describe('given one drive', function () {
it('should return contains image error', function () {
m.chai.expect(constraints.getListDriveImageCompatibilityStatuses([ drives[0] ], image)).to.deep.equal([
{
message: 'Drive Contains Image',
type: 2
}
])
})
it('should return locked error', function () {
m.chai.expect(constraints.getListDriveImageCompatibilityStatuses([ drives[1] ], image)).to.deep.equal([
{
message: 'Locked',
type: 2
}
])
})
it('should return too small for image error', function () {
m.chai.expect(constraints.getListDriveImageCompatibilityStatuses([ drives[2] ], image)).to.deep.equal([
{
message: 'Insufficient space, additional 1 B required',
type: 2
}
])
})
it('should return system drive warning', function () {
m.chai.expect(constraints.getListDriveImageCompatibilityStatuses([ drives[3] ], image)).to.deep.equal([
{
message: 'System Drive',
type: 1
}
])
})
it('should return large drive warning', function () {
m.chai.expect(constraints.getListDriveImageCompatibilityStatuses([ drives[4] ], image)).to.deep.equal([
{
message: 'Large Drive',
type: 1
}
])
})
it('should return not recommended warning', function () {
m.chai.expect(constraints.getListDriveImageCompatibilityStatuses([ drives[5] ], image)).to.deep.equal([
{
message: 'Not Recommended',
type: 1
}
])
})
})
describe('given multiple drives with all warnings/errors', function () {
it('should return all statuses', function () {
m.chai.expect(constraints.getListDriveImageCompatibilityStatuses(drives, image)).to.deep.equal([
{
message: 'Drive Contains Image',
type: 2
},
{
message: 'Locked',
type: 2
},
{
message: 'Insufficient space, additional 1 B required',
type: 2
},
{
message: 'System Drive',
type: 1
},
{
message: 'Large Drive',
type: 1
},
{
message: 'Not Recommended',
type: 1
}
])
})
})
})
describe('.hasListDriveImageCompatibilityStatus()', function () {
const drivePaths = process.platform === 'win32'
? [ 'E:\\', 'F:\\', 'G:\\', 'H:\\', 'J:\\', 'K:\\' ]
: [ '/dev/disk1', '/dev/disk2', '/dev/disk3', '/dev/disk4', '/dev/disk5', '/dev/disk6' ]
const drives = [
{
device: drivePaths[0],
description: 'My Drive',
size: 123456789,
displayName: drivePaths[0],
mountpoints: [ { path: __dirname } ],
isSystem: false,
isReadOnly: false
},
{
device: drivePaths[1],
description: 'My Other Drive',
size: 123456789,
displayName: drivePaths[1],
mountpoints: [],
isSystem: false,
isReadOnly: true
},
{
device: drivePaths[2],
description: 'My Drive',
size: 1234567,
displayName: drivePaths[2],
mountpoints: [],
isSystem: false,
isReadOnly: false
},
{
device: drivePaths[3],
description: 'My Drive',
size: 123456789,
displayName: drivePaths[3],
mountpoints: [],
isSystem: true,
isReadOnly: false
},
{
device: drivePaths[4],
description: 'My Drive',
size: 64000000001,
displayName: drivePaths[4],
mountpoints: [],
isSystem: false,
isReadOnly: false
},
{
device: drivePaths[5],
description: 'My Drive',
size: 12345678,
displayName: drivePaths[5],
mountpoints: [],
isSystem: false,
isReadOnly: false
},
{
device: drivePaths[6],
description: 'My Drive',
size: 123456789,
displayName: drivePaths[6],
mountpoints: [],
isSystem: false,
isReadOnly: false
}
]
const image = {
path: path.join(__dirname, 'rpi.img'),
size: {
original: drives[2].size + 1,
final: {
estimation: false,
value: drives[2].size + 1
}
},
recommendedDriveSize: drives[5].size + 1
}
describe('given no drives', function () {
it('should return false', function () {
m.chai.expect(constraints.hasListDriveImageCompatibilityStatus([], image)).to.be.false
})
})
describe('given one drive', function () {
it('should return true given a drive that contains the image', function () {
m.chai.expect(constraints.hasListDriveImageCompatibilityStatus([ drives[0] ], image)).to.be.true
})
it('should return true given a drive that is locked', function () {
m.chai.expect(constraints.hasListDriveImageCompatibilityStatus([ drives[1] ], image)).to.be.true
})
it('should return true given a drive that is too small for the image', function () {
m.chai.expect(constraints.hasListDriveImageCompatibilityStatus([ drives[2] ], image)).to.be.true
})
it('should return true given a drive that is a system drive', function () {
m.chai.expect(constraints.hasListDriveImageCompatibilityStatus([ drives[3] ], image)).to.be.true
})
it('should return true given a drive that is large', function () {
m.chai.expect(constraints.hasListDriveImageCompatibilityStatus([ drives[4] ], image)).to.be.true
})
it('should return true given a drive that is not recommended', function () {
m.chai.expect(constraints.hasListDriveImageCompatibilityStatus([ drives[5] ], image)).to.be.true
})
it('should return false given a drive with no warnings or errors', function () {
m.chai.expect(constraints.hasListDriveImageCompatibilityStatus([ drives[6] ], image)).to.be.false
})
})
describe('given many drives', function () {
it('should return true given some drives with errors or warnings', function () {
m.chai.expect(constraints.hasListDriveImageCompatibilityStatus(drives, image)).to.be.true
})
})
})
})

View File

@ -39,7 +39,7 @@ describe('Model: flashState', function () {
m.chai.expect(flashState.getFlashState()).to.deep.equal({
flashing: 0,
validating: 0,
verifying: 0,
succeeded: 0,
failed: 0,
percentage: 0,
@ -98,18 +98,6 @@ describe('Model: flashState', function () {
}).to.throw('Can\'t set the flashing state when not flashing')
})
it('should throw if type is not a string', function () {
flashState.setFlashingFlag()
m.chai.expect(function () {
flashState.setProgressState({
type: 1234,
percentage: 50,
eta: 15,
speed: 100000000000
})
}).to.throw('Invalid state type: 1234')
})
it('should not throw if percentage is 0', function () {
flashState.setFlashingFlag()
m.chai.expect(function () {
@ -261,7 +249,7 @@ describe('Model: flashState', function () {
const currentFlashState = flashState.getFlashState()
m.chai.expect(currentFlashState).to.deep.equal({
flashing: 0,
validating: 0,
verifying: 0,
succeeded: 0,
failed: 0,
percentage: 0,
@ -271,7 +259,10 @@ describe('Model: flashState', function () {
it('should return the current flash state', function () {
const state = {
type: 'write',
flashing: 1,
verifying: 0,
succeeded: 0,
failed: 0,
percentage: 50,
eta: 15,
speed: 0
@ -282,7 +273,7 @@ describe('Model: flashState', function () {
const currentFlashState = flashState.getFlashState()
m.chai.expect(currentFlashState).to.deep.equal({
flashing: 1,
validating: 0,
verifying: 0,
succeeded: 0,
failed: 0,
percentage: 50,
@ -383,7 +374,7 @@ describe('Model: flashState', function () {
m.chai.expect(flashState.getFlashState()).to.not.deep.equal({
flashing: 0,
validating: 0,
verifying: 0,
succeeded: 0,
failed: 0,
percentage: 0,
@ -397,7 +388,7 @@ describe('Model: flashState', function () {
m.chai.expect(flashState.getFlashState()).to.deep.equal({
flashing: 0,
validating: 0,
verifying: 0,
succeeded: 0,
failed: 0,
percentage: 0,

View File

@ -74,6 +74,10 @@ describe('Model: selectionState', function () {
const hasImage = selectionState.hasImage()
m.chai.expect(hasImage).to.be.false
})
it('.getSelectedDrives() should return []', function () {
m.chai.expect(selectionState.getSelectedDrives()).to.deep.equal([])
})
})
describe('given a drive', function () {
@ -96,6 +100,10 @@ describe('Model: selectionState', function () {
selectionState.selectDrive('/dev/disk2')
})
afterEach(function () {
selectionState.clear()
})
describe('.getCurrentDrive()', function () {
it('should return the drive', function () {
const drive = selectionState.getCurrentDrive()
@ -131,15 +139,26 @@ describe('Model: selectionState', function () {
})
describe('.deselectDrive()', function () {
it('should clear drives', function () {
it('should clear drive', 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('.getSelectedDrives()', function () {
it('should return that single selected drive', function () {
m.chai.expect(selectionState.getSelectedDrives()).to.deep.equal([
{
device: '/dev/disk2',
name: 'USB Drive',
size: 999999999,
isReadOnly: false
}
])
})
})
})
describe('given several drives', function () {
@ -237,6 +256,39 @@ describe('Model: selectionState', function () {
m.chai.expect(selectionState.getSelectedDevices()).to.deep.equal([ this.drives[0].device ])
})
})
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('.getSelectedDrives()', function () {
it('should return the selected drives', function () {
m.chai.expect(selectionState.getSelectedDrives()).to.deep.equal([
{
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
}
])
})
})
})
describe('given no drive', function () {