mirror of
https://github.com/balena-io/etcher.git
synced 2025-04-25 15:57:18 +00:00
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:
parent
8bd9ff58c4
commit
4140d49db3
@ -119,7 +119,6 @@ module.exports = function (
|
||||
previouslySelected: selectionState.isCurrentDrive(drive.device)
|
||||
})
|
||||
|
||||
selectionState.deselectOtherDrives(drive.device)
|
||||
selectionState.toggleDrive(drive.device)
|
||||
}
|
||||
})
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
})
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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(' ')
|
||||
},
|
||||
|
||||
|
@ -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'
|
||||
])
|
||||
|
@ -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
|
||||
|
@ -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)) {
|
||||
|
@ -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...')
|
||||
|
@ -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')
|
||||
})
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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,
|
||||
|
@ -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 () {
|
||||
|
Loading…
x
Reference in New Issue
Block a user