diff --git a/lib/gui/app/app.ts b/lib/gui/app/app.ts index 6443d943..ab4325f8 100644 --- a/lib/gui/app/app.ts +++ b/lib/gui/app/app.ts @@ -23,7 +23,11 @@ import * as ReactDOM from 'react-dom'; import { v4 as uuidV4 } from 'uuid'; import * as packageJSON from '../../../package.json'; -import { isDriveValid, isSourceDrive } from '../../shared/drive-constraints'; +import { + DrivelistDrive, + isDriveValid, + isSourceDrive, +} from '../../shared/drive-constraints'; import * as EXIT_CODES from '../../shared/exit-codes'; import * as messages from '../../shared/messages'; import * as availableDrives from './models/available-drives'; @@ -231,12 +235,12 @@ function prepareDrive(drive: Drive) { } } -function setDrives(drives: _.Dictionary) { +function setDrives(drives: _.Dictionary) { availableDrives.setDrives(_.values(drives)); } function getDrives() { - return _.keyBy(availableDrives.getDrives() || [], 'device'); + return _.keyBy(availableDrives.getDrives(), 'device'); } async function addDrive(drive: Drive) { diff --git a/lib/gui/app/components/drive-selector/drive-selector.tsx b/lib/gui/app/components/drive-selector/drive-selector.tsx index 290db14c..8bd3daef 100644 --- a/lib/gui/app/components/drive-selector/drive-selector.tsx +++ b/lib/gui/app/components/drive-selector/drive-selector.tsx @@ -289,8 +289,8 @@ export class DriveSelector extends React.Component< { field: 'description', key: 'extra', - // Space as empty string would use the field name as label - label: , + // We use an empty React fragment otherwise it uses the field name as label + label: <>, render: (_description: string, drive: Drive) => { if (isUsbbootDrive(drive)) { return this.renderProgress(drive.progress); diff --git a/lib/gui/app/components/drive-status-warning-modal/drive-status-warning-modal.tsx b/lib/gui/app/components/drive-status-warning-modal/drive-status-warning-modal.tsx index 451a74fb..4000917e 100644 --- a/lib/gui/app/components/drive-status-warning-modal/drive-status-warning-modal.tsx +++ b/lib/gui/app/components/drive-status-warning-modal/drive-status-warning-modal.tsx @@ -66,7 +66,7 @@ const DriveStatusWarningModal = ({ <> {middleEllipsis(drive.description, 28)}{' '} - {prettyBytes(drive.size || 0)}{' '} + {drive.size && prettyBytes(drive.size) + ' '} {drive.statuses[0].message} {i !== array.length - 1 ?
: null} diff --git a/lib/gui/app/components/reduced-flashing-infos/reduced-flashing-infos.tsx b/lib/gui/app/components/reduced-flashing-infos/reduced-flashing-infos.tsx index 527f45fc..539c3b2f 100644 --- a/lib/gui/app/components/reduced-flashing-infos/reduced-flashing-infos.tsx +++ b/lib/gui/app/components/reduced-flashing-infos/reduced-flashing-infos.tsx @@ -23,8 +23,8 @@ import { SVGIcon } from '../svg-icon/svg-icon'; import { middleEllipsis } from '../../utils/middle-ellipsis'; interface ReducedFlashingInfosProps { - imageLogo: string; - imageName: string; + imageLogo?: string; + imageName?: string; imageSize: string; driveTitle: string; driveLabel: string; @@ -40,6 +40,7 @@ export class ReducedFlashingInfos extends React.Component< } public render() { + const { imageName = '' } = this.props; return ( - {middleEllipsis(this.props.imageName, 16)} + {middleEllipsis(imageName, 16)} {this.props.imageSize} diff --git a/lib/gui/app/components/source-selector/source-selector.tsx b/lib/gui/app/components/source-selector/source-selector.tsx index c07fd83c..2e8dc3d6 100644 --- a/lib/gui/app/components/source-selector/source-selector.tsx +++ b/lib/gui/app/components/source-selector/source-selector.tsx @@ -254,6 +254,7 @@ export interface SourceMetadata extends sourceDestination.Metadata { SourceType: Source; drive?: DrivelistDrive; extension?: string; + archiveExtension?: string; } interface SourceSelectorProps { @@ -262,8 +263,8 @@ interface SourceSelectorProps { interface SourceSelectorState { hasImage: boolean; - imageName: string; - imageSize: number; + imageName?: string; + imageSize?: number; warning: { message: string; title: string | null } | null; showImageDetails: boolean; showURLSelector: boolean; @@ -543,7 +544,7 @@ export class SourceSelector extends React.Component< const imagePath = image.path || image.displayName || ''; const imageBasename = path.basename(imagePath); const imageName = image.name || ''; - const imageSize = image.size || 0; + const imageSize = image.size; const imageLogo = image.logo || ''; return ( @@ -585,7 +586,9 @@ export class SourceSelector extends React.Component< Remove )} - {prettyBytes(imageSize)} + {!_.isNil(imageSize) && ( + {prettyBytes(imageSize)} + )} ) : ( <> diff --git a/lib/gui/app/components/svg-icon/svg-icon.tsx b/lib/gui/app/components/svg-icon/svg-icon.tsx index bcf2d777..3059fe8e 100644 --- a/lib/gui/app/components/svg-icon/svg-icon.tsx +++ b/lib/gui/app/components/svg-icon/svg-icon.tsx @@ -37,8 +37,9 @@ function tryParseSVGContents(contents?: string): string | undefined { } interface SVGIconProps { - // List of embedded SVG contents to be tried in succession if any fails - contents: string; + // Optional string representing the SVG contents to be tried + contents?: string; + // Fallback SVG element to show if `contents` is invalid/undefined fallback: React.FunctionComponent>; // SVG image width unit width?: string; diff --git a/lib/gui/app/components/target-selector/target-selector-button.tsx b/lib/gui/app/components/target-selector/target-selector-button.tsx index 4a05cea4..1b529b45 100644 --- a/lib/gui/app/components/target-selector/target-selector-button.tsx +++ b/lib/gui/app/components/target-selector/target-selector-button.tsx @@ -96,7 +96,9 @@ export function TargetSelectorButton(props: TargetSelectorProps) { Change )} - {prettyBytes(target.size)} + {target.size != null && ( + {prettyBytes(target.size)} + )} ); } @@ -110,16 +112,16 @@ export function TargetSelectorButton(props: TargetSelectorProps) { targetsTemplate.push( - {warnings.length && ( + {warnings.length > 0 ? ( - )} + ) : null} {middleEllipsis(target.description, 14)} - {prettyBytes(target.size)} + {target.size != null && {prettyBytes(target.size)}} , ); } diff --git a/lib/gui/app/models/available-drives.ts b/lib/gui/app/models/available-drives.ts index 7acc4a55..0bff74fb 100644 --- a/lib/gui/app/models/available-drives.ts +++ b/lib/gui/app/models/available-drives.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { DrivelistDrive } from '../../../shared/drive-constraints'; import { Actions, store } from './store'; export function hasAvailableDrives() { @@ -27,6 +28,6 @@ export function setDrives(drives: any[]) { }); } -export function getDrives(): any[] { +export function getDrives(): DrivelistDrive[] { return store.getState().toJS().availableDrives; } diff --git a/lib/gui/app/models/selection-state.ts b/lib/gui/app/models/selection-state.ts index a7de51aa..959cf828 100644 --- a/lib/gui/app/models/selection-state.ts +++ b/lib/gui/app/models/selection-state.ts @@ -1,3 +1,4 @@ +import { DrivelistDrive } from '../../../shared/drive-constraints'; /* * Copyright 2016 balena.io * @@ -40,7 +41,7 @@ export function toggleDrive(driveDevice: string) { } } -export function selectSource(source: any) { +export function selectSource(source: SourceMetadata) { store.dispatch({ type: Actions.SELECT_SOURCE, data: source, @@ -57,11 +58,11 @@ export function getSelectedDevices(): string[] { /** * @summary Get all selected drive objects */ -export function getSelectedDrives(): any[] { - const drives = availableDrives.getDrives(); - return getSelectedDevices().map((device) => { - return drives.find((drive) => drive.device === device); - }); +export function getSelectedDrives(): DrivelistDrive[] { + const selectedDevices = getSelectedDevices(); + return availableDrives + .getDrives() + .filter((drive) => selectedDevices.includes(drive.device)); } /** @@ -71,32 +72,24 @@ export function getImage(): SourceMetadata | undefined { return store.getState().toJS().selection.image; } -export function getImagePath(): string { - return store.getState().toJS().selection.image?.path; +export function getImagePath() { + return getImage()?.path; } -export function getImageSize(): number { - return store.getState().toJS().selection.image?.size; +export function getImageSize() { + return getImage()?.size; } -export function getImageUrl(): string { - return store.getState().toJS().selection.image?.url; +export function getImageName() { + return getImage()?.name; } -export function getImageName(): string { - return store.getState().toJS().selection.image?.name; +export function getImageLogo() { + return getImage()?.logo; } -export function getImageLogo(): string { - return store.getState().toJS().selection.image?.logo; -} - -export function getImageSupportUrl(): string { - return store.getState().toJS().selection.image?.supportUrl; -} - -export function getImageRecommendedDriveSize(): number { - return store.getState().toJS().selection.image?.recommendedDriveSize; +export function getImageSupportUrl() { + return getImage()?.supportUrl; } /** diff --git a/lib/gui/app/pages/main/Flash.tsx b/lib/gui/app/pages/main/Flash.tsx index 62ed0637..57c4b4f3 100644 --- a/lib/gui/app/pages/main/Flash.tsx +++ b/lib/gui/app/pages/main/Flash.tsx @@ -198,18 +198,12 @@ export class FlashStep extends React.PureComponent< } private async tryFlash() { - const devices = selection.getSelectedDevices(); - const drives = availableDrives - .getDrives() - .filter((drive: { device: string }) => { - return devices.includes(drive.device); - }) - .map((drive) => { - return { - ...drive, - statuses: constraints.getDriveImageCompatibilityStatuses(drive), - }; - }); + const drives = selection.getSelectedDrives().map((drive) => { + return { + ...drive, + statuses: constraints.getDriveImageCompatibilityStatuses(drive), + }; + }); if (drives.length === 0 || this.props.isFlashing) { return; } diff --git a/lib/gui/app/pages/main/MainPage.tsx b/lib/gui/app/pages/main/MainPage.tsx index d23a6874..47e2c9da 100644 --- a/lib/gui/app/pages/main/MainPage.tsx +++ b/lib/gui/app/pages/main/MainPage.tsx @@ -103,9 +103,9 @@ interface MainPageStateFromStore { isFlashing: boolean; hasImage: boolean; hasDrive: boolean; - imageLogo: string; - imageSize: number; - imageName: string; + imageLogo?: string; + imageSize?: number; + imageName?: string; driveTitle: string; driveLabel: string; } @@ -272,7 +272,7 @@ export class MainPage extends React.Component< imageName={this.state.imageName} imageSize={ typeof this.state.imageSize === 'number' - ? (prettyBytes(this.state.imageSize) as string) + ? prettyBytes(this.state.imageSize) : '' } driveTitle={this.state.driveTitle} diff --git a/lib/shared/messages.ts b/lib/shared/messages.ts index b3cd838b..9bdd3372 100644 --- a/lib/shared/messages.ts +++ b/lib/shared/messages.ts @@ -15,6 +15,7 @@ */ import { Dictionary } from 'lodash'; +import { outdent } from 'outdent'; import * as prettyBytes from 'pretty-bytes'; export const progress: Dictionary<(quantity: number) => string> = { @@ -84,10 +85,10 @@ export const warning = { image: { recommendedDriveSize: number }, drive: { device: string; size: number }, ) => { - return [ - `This image recommends a ${prettyBytes(image.recommendedDriveSize)}`, - `drive, however ${drive.device} is only ${prettyBytes(drive.size)}.`, - ].join(' '); + return outdent({ newline: ' ' })` + This image recommends a ${prettyBytes(image.recommendedDriveSize)} + drive, however ${drive.device} is only ${prettyBytes(drive.size)}. + `; }, exitWhileFlashing: () => { @@ -150,10 +151,11 @@ export const error = { }, openSource: (sourceName: string, errorMessage: string) => { - return [ - `Something went wrong while opening ${sourceName}\n\n`, - `Error: ${errorMessage}`, - ].join(''); + return outdent` + Something went wrong while opening ${sourceName} + + Error: ${errorMessage} + `; }, flashFailure: ( diff --git a/tests/gui/models/available-drives.spec.ts b/tests/gui/models/available-drives.spec.ts index 126a4640..81b9933e 100644 --- a/tests/gui/models/available-drives.spec.ts +++ b/tests/gui/models/available-drives.spec.ts @@ -15,6 +15,7 @@ */ import { expect } from 'chai'; +import { File } from 'etcher-sdk/build/source-destination'; import * as path from 'path'; import * as availableDrives from '../../../lib/gui/app/models/available-drives'; @@ -158,10 +159,13 @@ describe('Model: availableDrives', function () { selectionState.clear(); selectionState.selectSource({ + description: this.imagePath.split('/').pop(), + displayName: this.imagePath, path: this.imagePath, extension: 'img', size: 999999999, isSizeEstimated: false, + SourceType: File, recommendedDriveSize: 2000000000, }); }); diff --git a/tests/gui/models/selection-state.spec.ts b/tests/gui/models/selection-state.spec.ts index 234dde8b..3e28f8c4 100644 --- a/tests/gui/models/selection-state.spec.ts +++ b/tests/gui/models/selection-state.spec.ts @@ -15,11 +15,13 @@ */ import { expect } from 'chai'; -import * as _ from 'lodash'; +import { File } from 'etcher-sdk/build/source-destination'; import * as path from 'path'; +import { SourceMetadata } from '../../../lib/gui/app/components/source-selector/source-selector'; import * as availableDrives from '../../../lib/gui/app/models/available-drives'; import * as selectionState from '../../../lib/gui/app/models/selection-state'; +import { DrivelistDrive } from '../../../lib/shared/drive-constraints'; describe('Model: selectionState', function () { describe('given a clean state', function () { @@ -39,10 +41,6 @@ describe('Model: selectionState', function () { expect(selectionState.getImageSize()).to.be.undefined; }); - it('getImageUrl() should return undefined', function () { - expect(selectionState.getImageUrl()).to.be.undefined; - }); - it('getImageName() should return undefined', function () { expect(selectionState.getImageName()).to.be.undefined; }); @@ -55,10 +53,6 @@ describe('Model: selectionState', function () { expect(selectionState.getImageSupportUrl()).to.be.undefined; }); - it('getImageRecommendedDriveSize() should return undefined', function () { - expect(selectionState.getImageRecommendedDriveSize()).to.be.undefined; - }); - it('hasDrive() should return false', function () { const hasDrive = selectionState.hasDrive(); expect(hasDrive).to.be.false; @@ -138,10 +132,10 @@ describe('Model: selectionState', function () { it('should queue the drive', function () { selectionState.selectDrive('/dev/disk5'); const drives = selectionState.getSelectedDevices(); - const lastDriveDevice = _.last(drives); - const lastDrive = _.find(availableDrives.getDrives(), { - device: lastDriveDevice, - }); + const lastDriveDevice = drives.pop(); + const lastDrive = availableDrives + .getDrives() + .find((drive) => drive.device === lastDriveDevice); expect(lastDrive).to.deep.equal({ device: '/dev/disk5', name: 'USB Drive', @@ -214,7 +208,7 @@ describe('Model: selectionState', function () { it('should be able to add more drives', function () { selectionState.selectDrive(this.drives[2].device); expect(selectionState.getSelectedDevices()).to.deep.equal( - _.map(this.drives, 'device'), + this.drives.map((drive: DrivelistDrive) => drive.device), ); }); @@ -234,13 +228,13 @@ describe('Model: selectionState', function () { system: true, }; - const newDrives = [..._.initial(this.drives), systemDrive]; + const newDrives = [...this.drives.slice(0, -1), systemDrive]; availableDrives.setDrives(newDrives); selectionState.selectDrive(systemDrive.device); availableDrives.setDrives(newDrives); expect(selectionState.getSelectedDevices()).to.deep.equal( - _.map(newDrives, 'device'), + newDrives.map((drive: DrivelistDrive) => drive.device), ); }); @@ -271,6 +265,12 @@ describe('Model: selectionState', function () { describe('.getSelectedDrives()', function () { it('should return the selected drives', function () { expect(selectionState.getSelectedDrives()).to.deep.equal([ + { + device: '/dev/disk2', + name: 'USB Drive 2', + size: 999999999, + isReadOnly: false, + }, { device: '/dev/sdb', description: 'DataTraveler 2.0', @@ -280,12 +280,6 @@ describe('Model: selectionState', function () { system: false, isReadOnly: false, }, - { - device: '/dev/disk2', - name: 'USB Drive 2', - size: 999999999, - isReadOnly: false, - }, ]); }); }); @@ -399,13 +393,6 @@ describe('Model: selectionState', function () { }); }); - describe('.getImageUrl()', function () { - it('should return the image url', function () { - const imageUrl = selectionState.getImageUrl(); - expect(imageUrl).to.equal('https://www.raspbian.org'); - }); - }); - describe('.getImageName()', function () { it('should return the image name', function () { const imageName = selectionState.getImageName(); @@ -429,13 +416,6 @@ describe('Model: selectionState', function () { }); }); - describe('.getImageRecommendedDriveSize()', function () { - it('should return the image recommended drive size', function () { - const imageRecommendedDriveSize = selectionState.getImageRecommendedDriveSize(); - expect(imageRecommendedDriveSize).to.equal(1000000000); - }); - }); - describe('.hasImage()', function () { it('should return true', function () { const hasImage = selectionState.hasImage(); @@ -446,10 +426,13 @@ describe('Model: selectionState', function () { describe('.selectImage()', function () { it('should override the image', function () { selectionState.selectSource({ + description: 'bar.img', + displayName: 'bar.img', path: 'bar.img', extension: 'img', size: 999999999, isSizeEstimated: false, + SourceType: File, }); const imagePath = selectionState.getImagePath(); @@ -475,13 +458,19 @@ describe('Model: selectionState', function () { describe('.selectImage()', function () { afterEach(selectionState.clear); + const image: SourceMetadata = { + description: 'foo.img', + displayName: 'foo.img', + path: 'foo.img', + extension: 'img', + size: 999999999, + isSizeEstimated: false, + SourceType: File, + recommendedDriveSize: 2000000000, + }; + it('should be able to set an image', function () { - selectionState.selectSource({ - path: 'foo.img', - extension: 'img', - size: 999999999, - isSizeEstimated: false, - }); + selectionState.selectSource(image); const imagePath = selectionState.getImagePath(); expect(imagePath).to.equal('foo.img'); @@ -491,11 +480,9 @@ describe('Model: selectionState', function () { it('should be able to set an image with an archive extension', function () { selectionState.selectSource({ + ...image, path: 'foo.zip', - extension: 'img', archiveExtension: 'zip', - size: 999999999, - isSizeEstimated: false, }); const imagePath = selectionState.getImagePath(); @@ -504,11 +491,9 @@ describe('Model: selectionState', function () { it('should infer a compressed raw image if the penultimate extension is missing', function () { selectionState.selectSource({ + ...image, path: 'foo.xz', - extension: 'img', archiveExtension: 'xz', - size: 999999999, - isSizeEstimated: false, }); const imagePath = selectionState.getImagePath(); @@ -517,53 +502,19 @@ describe('Model: selectionState', function () { it('should infer a compressed raw image if the penultimate extension is not a file extension', function () { selectionState.selectSource({ + ...image, path: 'something.linux-x86-64.gz', - extension: 'img', archiveExtension: 'gz', - size: 999999999, - isSizeEstimated: false, }); const imagePath = selectionState.getImagePath(); expect(imagePath).to.equal('something.linux-x86-64.gz'); }); - it('should throw if no path', function () { - expect(function () { - selectionState.selectSource({ - extension: 'img', - size: 999999999, - isSizeEstimated: false, - }); - }).to.throw('Missing image fields: path'); - }); - - it('should throw if path is not a string', function () { - expect(function () { - selectionState.selectSource({ - path: 123, - extension: 'img', - size: 999999999, - isSizeEstimated: false, - }); - }).to.throw('Invalid image path: 123'); - }); - - it('should throw if the original size is not a number', function () { - expect(function () { - selectionState.selectSource({ - path: 'foo.img', - extension: 'img', - size: 999999999, - compressedSize: '999999999', - isSizeEstimated: false, - }); - }).to.throw('Invalid image compressed size: 999999999'); - }); - it('should throw if the original size is a float number', function () { expect(function () { selectionState.selectSource({ + ...image, path: 'foo.img', extension: 'img', size: 999999999, @@ -576,33 +527,17 @@ describe('Model: selectionState', function () { it('should throw if the original size is negative', function () { expect(function () { selectionState.selectSource({ - path: 'foo.img', - extension: 'img', - size: 999999999, + ...image, compressedSize: -1, - isSizeEstimated: false, }); }).to.throw('Invalid image compressed size: -1'); }); - it('should throw if the final size is not a number', function () { - expect(function () { - selectionState.selectSource({ - path: 'foo.img', - extension: 'img', - size: '999999999', - isSizeEstimated: false, - }); - }).to.throw('Invalid image size: 999999999'); - }); - it('should throw if the final size is a float number', function () { expect(function () { selectionState.selectSource({ - path: 'foo.img', - extension: 'img', + ...image, size: 999999999.999, - isSizeEstimated: false, }); }).to.throw('Invalid image size: 999999999.999'); }); @@ -610,50 +545,12 @@ describe('Model: selectionState', function () { it('should throw if the final size is negative', function () { expect(function () { selectionState.selectSource({ - path: 'foo.img', - extension: 'img', + ...image, size: -1, - isSizeEstimated: false, }); }).to.throw('Invalid image size: -1'); }); - it("should throw if url is defined but it's not a string", function () { - expect(function () { - selectionState.selectSource({ - path: 'foo.img', - extension: 'img', - size: 999999999, - isSizeEstimated: false, - url: 1234, - }); - }).to.throw('Invalid image url: 1234'); - }); - - it("should throw if name is defined but it's not a string", function () { - expect(function () { - selectionState.selectSource({ - path: 'foo.img', - extension: 'img', - size: 999999999, - isSizeEstimated: false, - name: 1234, - }); - }).to.throw('Invalid image name: 1234'); - }); - - it("should throw if logo is defined but it's not a string", function () { - expect(function () { - selectionState.selectSource({ - path: 'foo.img', - extension: 'img', - size: 999999999, - isSizeEstimated: false, - logo: 1234, - }); - }).to.throw('Invalid image logo: 1234'); - }); - it('should de-select a previously selected not-large-enough drive', function () { availableDrives.setDrives([ { @@ -668,10 +565,8 @@ describe('Model: selectionState', function () { expect(selectionState.hasDrive()).to.be.true; selectionState.selectSource({ - path: 'foo.img', - extension: 'img', + ...image, size: 1234567890, - isSizeEstimated: false, }); expect(selectionState.hasDrive()).to.be.false; @@ -692,10 +587,7 @@ describe('Model: selectionState', function () { expect(selectionState.hasDrive()).to.be.true; selectionState.selectSource({ - path: 'foo.img', - extension: 'img', - size: 999999999, - isSizeEstimated: false, + ...image, recommendedDriveSize: 1500000000, }); @@ -727,10 +619,10 @@ describe('Model: selectionState', function () { expect(selectionState.hasDrive()).to.be.true; selectionState.selectSource({ + ...image, path: imagePath, extension: 'img', size: 999999999, - isSizeEstimated: false, }); expect(selectionState.hasDrive()).to.be.false; @@ -740,6 +632,16 @@ describe('Model: selectionState', function () { }); describe('given a drive and an image', function () { + const image: SourceMetadata = { + description: 'foo.img', + displayName: 'foo.img', + path: 'foo.img', + extension: 'img', + size: 999999999, + SourceType: File, + isSizeEstimated: false, + }; + beforeEach(function () { availableDrives.setDrives([ { @@ -752,12 +654,7 @@ describe('Model: selectionState', function () { selectionState.selectDrive('/dev/disk1'); - selectionState.selectSource({ - path: 'foo.img', - extension: 'img', - size: 999999999, - isSizeEstimated: false, - }); + selectionState.selectSource(image); }); describe('.clear()', function () { @@ -824,6 +721,16 @@ describe('Model: selectionState', function () { }); describe('given several drives', function () { + const image: SourceMetadata = { + description: 'foo.img', + displayName: 'foo.img', + path: 'foo.img', + extension: 'img', + size: 999999999, + SourceType: File, + isSizeEstimated: false, + }; + beforeEach(function () { availableDrives.setDrives([ { @@ -850,12 +757,7 @@ describe('Model: selectionState', function () { selectionState.selectDrive('/dev/disk2'); selectionState.selectDrive('/dev/disk3'); - selectionState.selectSource({ - path: 'foo.img', - extension: 'img', - size: 999999999, - isSizeEstimated: false, - }); + selectionState.selectSource(image); }); describe('.clear()', function () {