Move the main controller to React

Change-type: patch
Signed-off-by: Stevche Radevski <stevche@balena.io>
This commit is contained in:
Stevche Radevski 2019-12-05 11:10:26 +01:00 committed by Lorenzo Alberto Maria Ambrosi
parent 84fe5004a9
commit 8e47829905
14 changed files with 244 additions and 673 deletions

View File

@ -90,7 +90,7 @@ const app = angular.module('Etcher', [
require('./components/safe-webview'),
// Pages
require('./pages/main/main'),
require('./pages/main/main.ts').MODULE_NAME,
require('./pages/finish/finish'),
require('./components/settings/index.ts').MODULE_NAME,

View File

@ -1,34 +0,0 @@
/*
* Copyright 2016 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict'
/**
* @module Etcher.Components.FeaturedProject
*/
const angular = require('angular')
const { react2angular } = require('react2angular')
const MODULE_NAME = 'Etcher.Components.FeaturedProject'
const FeaturedProject = angular.module(MODULE_NAME, [])
FeaturedProject.component(
'featuredProject',
react2angular(require('./featured-project.jsx'))
)
module.exports = MODULE_NAME

View File

@ -28,7 +28,6 @@ const messages = require('../../../../shared/messages')
const supportedFormats = require('../../../../shared/supported-formats')
const shared = require('../../../../shared/units')
const selectionState = require('../../models/selection-state')
const settings = require('../../models/settings')
const store = require('../../models/store')
const analytics = require('../../modules/analytics')
const exceptionReporter = require('../../modules/exception-reporter')
@ -41,8 +40,7 @@ const {
Footer,
Underline,
DetailsText,
ChangeButton,
ThemedProvider
ChangeButton
} = require('../../styled-components')
const {
Modal
@ -306,7 +304,7 @@ class ImageSelector extends React.Component {
const imageSize = selectionState.getImageSize()
return (
<ThemedProvider>
<React.Fragment>
<div className="box text-center relative">
<Dropzone multiple={false} onDrop={this.handleOnDrop}>
{({ getRootProps, getInputProps }) => (
@ -394,7 +392,7 @@ class ImageSelector extends React.Component {
{selectionState.getImagePath()}
</Modal>
)}
</ThemedProvider>
</React.Fragment>
)
}
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2018 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict'
/* eslint-disable jsdoc/require-example */
/**
* @module Etcher.Components.ImageSelector
*/
const angular = require('angular')
const { react2angular } = require('react2angular')
const MODULE_NAME = 'Etcher.Components.ImageSelector'
const ImageSelector = angular.module(MODULE_NAME, [])
ImageSelector.component(
'imageSelector',
react2angular(require('./image-selector.jsx')),
[],
[
'WarningModalService'
]
)
module.exports = MODULE_NAME

View File

@ -1,34 +0,0 @@
/*
* Copyright 2016 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict'
/**
* @module Etcher.Components.ReducedFlashingInfos
*/
const angular = require('angular')
const { react2angular } = require('react2angular')
const MODULE_NAME = 'Etcher.Components.ReducedFlashingInfos'
const ReducedFlashingInfos = angular.module(MODULE_NAME, [])
ReducedFlashingInfos.component(
'reducedFlashingInfos',
react2angular(require('./reduced-flashing-infos.jsx'))
)
module.exports = MODULE_NAME

View File

@ -17,15 +17,11 @@
'use strict'
const _ = require('lodash')
const prettyBytes = require('pretty-bytes')
const propTypes = require('prop-types')
const React = require('react')
const {
ThemedProvider
} = require('../../styled-components')
const driveConstraints = require('../../../../shared/drive-constraints')
const utils = require('../../../../shared/utils')
const TargetSelector = require('../../components/drive-selector/target-selector')
const TargetSelector = require('../../components/drive-selector/target-selector.jsx')
const SvgIcon = require('../../components/svg-icon/svg-icon.jsx')
const selectionState = require('../../models/selection-state')
const settings = require('../../models/settings')
@ -150,44 +146,42 @@ const DriveSelector = ({
const showStepConnectingLines = !webviewShowing || !flashing
return (
<ThemedProvider>
<div className="box text-center relative">
<div className="box text-center relative">
{showStepConnectingLines && (
<React.Fragment>
<div
className="step-border-left"
disabled={disabled}
></div>
<div
className="step-border-right"
disabled={nextStepDisabled}
></div>
</React.Fragment>
)}
<div className="center-block">
<SvgIcon
paths={[ '../../assets/drive.svg' ]}
{showStepConnectingLines && (
<React.Fragment>
<div
className="step-border-left"
disabled={disabled}
/>
</div>
></div>
<div
className="step-border-right"
disabled={nextStepDisabled}
></div>
</React.Fragment>
)}
<div className="space-vertical-large">
<TargetSelector
disabled={disabled}
show={!hasDrive && showDrivesButton}
tooltip={driveListLabel}
selection={selectionState}
openDriveSelector={() => openDriveSelector(DriveSelectorService)}
reselectDrive={() => reselectDrive(DriveSelectorService)}
flashing={flashing}
constraints={driveConstraints}
targets={targets}
/>
</div>
<div className="center-block">
<SvgIcon
paths={[ '../../assets/drive.svg' ]}
disabled={disabled}
/>
</div>
</ThemedProvider>
<div className="space-vertical-large">
<TargetSelector
disabled={disabled}
show={!hasDrive && showDrivesButton}
tooltip={driveListLabel}
selection={selectionState}
openDriveSelector={() => openDriveSelector(DriveSelectorService)}
reselectDrive={() => reselectDrive(DriveSelectorService)}
flashing={flashing}
constraints={driveConstraints}
targets={targets}
/>
</div>
</div>
)
}

View File

@ -20,7 +20,6 @@ const React = require('react')
const _ = require('lodash')
const { Modal, Txt } = require('rendition')
const { ThemedProvider } = require('../../styled-components')
const messages = require('../../../../shared/messages')
const flashState = require('../../models/flash-state')
const driveScanner = require('../../modules/drive-scanner')
@ -93,7 +92,7 @@ const flashImageToDrive = async ($timeout, $state) => {
// otherwise Windows throws EPERM
driveScanner.stop()
const iconPath = '../../../assets/icon.png'
const iconPath = '../../assets/icon.png'
const basename = path.basename(image.path)
try {
await imageWriter.flash(image.path, drives)
@ -167,23 +166,13 @@ const Flash = ({
shouldFlashStepBeDisabled, lastFlashErrorCode, progressMessage,
$timeout, $state, DriveSelectorService
}) => {
// This is a hack to re-render the component whenever the global state changes. Remove once we get rid of angular and use redux correctly.
// eslint-disable-next-line no-magic-numbers
const setRefresh = React.useState(false)[1]
const state = flashState.getFlashState()
const isFlashing = flashState.isFlashing()
const isFlashStepDisabled = shouldFlashStepBeDisabled()
const flashErrorCode = lastFlashErrorCode()
const [ warningMessages, setWarningMessages ] = React.useState([])
const [ errorMessage, setErrorMessage ] = React.useState('')
React.useEffect(() => {
return store.observe(() => {
setRefresh((ref) => !ref)
})
}, [])
const handleWarningResponse = async (shouldContinue) => {
setWarningMessages([])
@ -229,10 +218,10 @@ const Flash = ({
setErrorMessage(await flashImageToDrive($timeout, $state))
}
return <ThemedProvider>
return <React.Fragment>
<div className="box text-center">
<div className="center-block">
<SvgIcon paths={[ '../../assets/flash.svg' ]} disabled={isFlashStepDisabled}/>
<SvgIcon paths={[ '../../assets/flash.svg' ]} disabled={shouldFlashStepBeDisabled}/>
</div>
<div className="space-vertical-large">
@ -242,7 +231,7 @@ const Flash = ({
active={isFlashing}
percentage={state.percentage}
label={getProgressButtonLabel()}
disabled={Boolean(flashErrorCode) || isFlashStepDisabled}
disabled={Boolean(flashErrorCode) || shouldFlashStepBeDisabled}
callback={tryFlash}>
</ProgressButton>
@ -300,7 +289,7 @@ const Flash = ({
</Modal>
}
</ThemedProvider>
</React.Fragment>
}
module.exports = Flash

View File

@ -0,0 +1,142 @@
/*
* Copyright 2019 balena.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as path from 'path';
import * as React from 'react';
import * as FeaturedProject from '../../components/featured-project/featured-project';
import * as ImageSelector from '../../components/image-selector/image-selector';
import * as ReducedFlashingInfos from '../../components/reduced-flashing-infos/reduced-flashing-infos';
import * as flashState from '../../models/flash-state';
import * as selectionState from '../../models/selection-state';
import * as store from '../../models/store';
import { ThemedProvider } from '../../styled-components';
import * as middleEllipsis from '../../utils/middle-ellipsis';
import * as messages from '../../../../shared/messages';
import { bytesToClosestUnit } from '../../../../shared/units';
import * as DriveSelector from './DriveSelector';
import * as Flash from './Flash';
const getDrivesTitle = (selection: any) => {
const drives = selection.getSelectedDrives();
if (drives.length === 1) {
return drives[0].description || 'Untitled Device';
}
if (drives.length === 0) {
return 'No targets found';
}
return `${drives.length} Targets`;
};
const getImageBasename = (selection: any) => {
if (!selection.hasImage()) {
return '';
}
const selectionImageName = selection.getImageName();
const imageBasename = path.basename(selection.getImagePath());
return selectionImageName || imageBasename;
};
const MainPage = ({ DriveSelectorService, $timeout, $state }: any) => {
const setRefresh = React.useState(false)[1];
const [isWebviewShowing, setIsWebviewShowing] = React.useState(false);
React.useEffect(() => {
return (store as any).observe(() => {
setRefresh(ref => !ref);
});
}, []);
const setWebviewShowing = (isShowing: boolean) => {
setIsWebviewShowing(isShowing);
store.dispatch({
type: 'SET_WEBVIEW_SHOWING_STATUS',
data: Boolean(isShowing),
});
};
const isFlashing = flashState.isFlashing();
const shouldDriveStepBeDisabled = !selectionState.hasImage();
const shouldFlashStepBeDisabled =
!selectionState.hasDrive() || shouldDriveStepBeDisabled;
const hasDrive = selectionState.hasDrive();
const imageLogo = selectionState.getImageLogo();
const imageSize = bytesToClosestUnit(selectionState.getImageSize());
const imageName = middleEllipsis(getImageBasename(selectionState), 16);
const driveTitle = middleEllipsis(getDrivesTitle(selectionState), 16);
const shouldShowFlashingInfos = isFlashing && isWebviewShowing;
const lastFlashErrorCode = flashState.getLastFlashErrorCode;
const progressMessage = messages.progress;
return (
<ThemedProvider style={{ display: 'flex', height: '100%' }}>
<div className="page-main row around-xs">
<div className="col-xs">
<ImageSelector flashing={isFlashing} />
</div>
<div className="col-xs">
<DriveSelector
DriveSelectorService={DriveSelectorService}
webviewShowing={isWebviewShowing}
disabled={shouldDriveStepBeDisabled}
nextStepDisabled={shouldFlashStepBeDisabled}
hasDrive={hasDrive}
flashing={isFlashing}
/>
</div>
{isFlashing && (
<div>
<FeaturedProject
className={
isFlashing && isWebviewShowing ? 'fp-visible' : undefined
}
onWebviewShow={setWebviewShowing}
/>
</div>
)}
<div>
<ReducedFlashingInfos
imageLogo={imageLogo}
imageName={imageName}
imageSize={imageSize}
driveTitle={driveTitle}
shouldShow={shouldShowFlashingInfos}
/>
</div>
<div className="col-xs">
<Flash
DriveSelectorService={DriveSelectorService}
$timeout={$timeout}
$state={$state}
shouldFlashStepBeDisabled={shouldFlashStepBeDisabled}
lastFlashErrorCode={lastFlashErrorCode}
progressMessage={progressMessage}
/>
</div>
</div>
</ThemedProvider>
);
};
export default MainPage;

View File

@ -1,165 +0,0 @@
/*
* Copyright 2016 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict'
const path = require('path')
const store = require('../../../models/store')
const settings = require('../../../models/settings')
const flashState = require('../../../models/flash-state')
const availableDrives = require('../../../models/available-drives')
const selectionState = require('../../../models/selection-state')
const driveConstraints = require('../../../../../shared/drive-constraints')
const messages = require('../../../../../shared/messages')
const prettyBytes = require('pretty-bytes')
module.exports = function (
OSOpenExternalService,
$filter,
$scope
) {
// Expose several modules to the template for convenience
this.selection = selectionState
this.drives = availableDrives
this.state = flashState
this.settings = settings
this.external = OSOpenExternalService
this.constraints = driveConstraints
this.progressMessage = messages.progress
this.isWebviewShowing = Boolean(store.getState().toJS().isWebviewShowing)
// Trigger an update if the store changes
store.observe(() => {
if (!$scope.$$phase) {
$scope.$apply()
}
})
/**
* @summary Determine if the drive step should be disabled
* @function
* @public
*
* @returns {Boolean} whether the drive step should be disabled
*
* @example
* if (MainController.shouldDriveStepBeDisabled()) {
* console.log('The drive step should be disabled');
* }
*/
this.shouldDriveStepBeDisabled = () => {
return !selectionState.hasImage()
}
/**
* @summary Determine if the flash step should be disabled
* @function
* @public
*
* @returns {Boolean} whether the flash step should be disabled
*
* @example
* if (MainController.shouldFlashStepBeDisabled()) {
* console.log('The flash step should be disabled');
* }
*/
this.shouldFlashStepBeDisabled = () => {
return !selectionState.hasDrive() || this.shouldDriveStepBeDisabled()
}
/**
* @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 = this.selection.getSelectedDrives()
/* eslint-disable no-magic-numbers */
if (drives.length === 1) {
return drives[0].description || 'Untitled Device'
}
/* eslint-enable no-magic-numbers */
// eslint-disable-next-line no-magic-numbers
if (drives.length === 0) {
return 'No targets found'
}
return `${drives.length} Targets`
}
/**
* @summary Get drive subtitle
* @function
* @public
*
* @returns {String} - drives subtitle
*
* @example
* console.log(MainController.getDrivesSubtitle())
* > '32 GB'
*/
this.getDrivesSubtitle = () => {
const drive = this.selection.getCurrentDrive()
if (drive) {
return prettyBytes(drive.size)
}
return 'Please insert at least one target device'
}
/**
* @summary Get the basename of the selected image
* @function
* @public
*
* @returns {String} basename of the selected image
*
* @example
* const imageBasename = ImageSelectionController.getImageBasename();
*/
this.getImageBasename = () => {
if (!this.selection.hasImage()) {
return ''
}
return path.basename(this.selection.getImagePath())
}
this.setWebviewShowing = (data) => {
this.isWebviewShowing = data
store.dispatch({
type: 'SET_WEBVIEW_SHOWING_STATUS',
data: Boolean(data)
})
}
this.getDriveTitle = () => {
/* eslint-disable no-magic-numbers */
const driveTitleRaw = (this.selection.getSelectedDevices().length === 1)
? this.getDrivesSubtitle()
: `${this.selection.getSelectedDevices().length} Targets`
return $filter('middleEllipsis:20')(driveTitleRaw)
}
}

View File

@ -1,70 +0,0 @@
/*
* Copyright 2016 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict'
/**
* This page represents the application main page.
*
* @module Etcher.Pages.Main
*/
const angular = require('angular')
const { react2angular } = require('react2angular')
const MODULE_NAME = 'Etcher.Pages.Main'
const MainPage = angular.module(MODULE_NAME, [
require('angular-ui-router'),
require('../../components/drive-selector/drive-selector'),
require('../../components/image-selector'),
require('../../components/featured-project'),
require('../../components/reduced-flashing-infos'),
require('../../components/flash-another'),
require('../../components/flash-results'),
require('../../components/drive-selector'),
require('../../os/open-external/open-external'),
require('../../utils/byte-size/byte-size'),
require('../../utils/middle-ellipsis/filter')
])
MainPage.controller('MainController', require('./controllers/main'))
MainPage.component('driveSelector', react2angular(require('./DriveSelector.jsx'),
[
'webviewShowing',
'disabled',
'nextStepDisabled',
'hasDrive',
'flashing'
],
[ 'DriveSelectorService' ]
))
MainPage.component('flash', react2angular(require('./Flash.jsx'),
[ 'shouldFlashStepBeDisabled', 'lastFlashErrorCode', 'progressMessage' ],
[ '$timeout', '$state', 'DriveSelectorService' ]))
MainPage.config(($stateProvider) => {
$stateProvider
.state('main', {
url: '/main',
controller: 'MainController as main',
template: require('./templates/main.tpl.html')
})
})
module.exports = MODULE_NAME

View File

@ -0,0 +1,60 @@
/*
* Copyright 2019 balena.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
/**
* This page represents the application main page.
*
* @module Etcher.Pages.Main
*/
import * as angular from 'angular';
// @ts-ignore
import * as angularRouter from 'angular-ui-router';
import { react2angular } from 'react2angular';
import MainPage from './MainPage';
import * as driveSelector from '../../components/drive-selector';
import * as driveSelectorService from '../../components/drive-selector/drive-selector';
import * as flashAnother from '../../components/flash-another';
import * as flashResults from '../../components/flash-results';
import * as openExternal from '../../os/open-external/open-external';
import * as byteSize from '../../utils/byte-size/byte-size';
export const MODULE_NAME = 'Etcher.Pages.Main';
const Main = angular.module(MODULE_NAME, [
angularRouter,
driveSelectorService,
flashAnother,
flashResults,
driveSelector,
openExternal,
byteSize,
]);
Main.component(
'mainPage',
react2angular(MainPage, [], ['DriveSelectorService', '$timeout', '$state']),
);
Main.config(($stateProvider: any) => {
$stateProvider.state('main', {
url: '/main',
template: '<main-page style="width:100%"></main-page>',
});
});

View File

@ -1,46 +0,0 @@
<div class="page-main row around-xs">
<div class="col-xs">
<image-selector
flashing="main.state.isFlashing()"
>
</image-selector>
</div>
<div class="col-xs">
<drive-selector
webview-showing="main.isWebviewShowing"
disabled="main.shouldDriveStepBeDisabled()"
next-step-disabled="main.shouldFlashStepBeDisabled()"
has-drive="main.selection.hasDrive()"
flashing="main.state.isFlashing()"
></drive-selector>
</div>
<div>
<featured-project
ng-if="main.state.isFlashing()"
ng-class="{
'fp-visible': main.state.isFlashing() && main.isWebviewShowing
}"
on-webview-show="main.setWebviewShowing"
></featured-project>
</div>
<div>
<reduced-flashing-infos
image-logo="main.selection.getImageLogo()"
image-name="main.selection.getImageName() || main.getImageBasename() | middleEllipsis:16"
image-size="main.selection.getImageSize() | closestUnit"
drive-title="main.getDrivesTitle() | middleEllipsis:16"
should-show="main.state.isFlashing() && main.isWebviewShowing"
></reduced-flashing-infos>
</div>
<div class="col-xs">
<flash
should-flash-step-be-disabled="main.shouldFlashStepBeDisabled"
last-flash-error-code="main.state.getLastFlashErrorCode"
progress-image="main.progressImage"
></flash>
</div>
</div>

View File

@ -1,40 +0,0 @@
/*
* Copyright 2016 Juan Cruz Viotti. https://github.com/jviotti
* Copyright 2018 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict'
/**
* The purpose of this module is to provide utilities
* to work with sizes in bytes.
*
* @module Etcher.Utils.MiddleEllipsis
*/
const _ = require('lodash')
const angular = require('angular')
const middleEllipsis = require('../middle-ellipsis')
const MODULE_NAME = 'Etcher.Utils.MiddleEllipsis'
const MiddleEllipsis = angular.module(MODULE_NAME, [])
/* eslint-disable lodash/prefer-lodash-method */
MiddleEllipsis.filter('middleEllipsis', _.constant(middleEllipsis))
/* eslint-enable lodash/prefer-lodash-method */
module.exports = MODULE_NAME

View File

@ -1,184 +0,0 @@
/*
* Copyright 2017 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict'
const m = require('mochainon')
const _ = require('lodash')
const fs = require('fs')
const angular = require('angular')
const availableDrives = require('../../../lib/gui/app/models/available-drives')
const selectionState = require('../../../lib/gui/app/models/selection-state')
// Mock HTML requires by reading from the file-system
// eslint-disable-next-line node/no-deprecated-api
require.extensions['.html'] = (module, filename) => {
module.exports = fs.readFileSync(filename, {
encoding: 'utf8'
})
}
// NOTE(Shou): since we don't test React yet we just ignore JSX files
// eslint-disable-next-line node/no-deprecated-api
require.extensions['.jsx'] = _.constant(null)
describe('Browser: MainPage', function () {
beforeEach(angular.mock.module(
require('../../../lib/gui/app/pages/main/main')
))
describe('MainController', function () {
let $controller
beforeEach(angular.mock.inject(function (_$controller_) {
$controller = _$controller_
}))
describe('.shouldDriveStepBeDisabled()', function () {
it('should return true if there is no drive', function () {
const controller = $controller('MainController', {
$scope: {
$apply: _.noop
}
})
selectionState.clear()
m.chai.expect(controller.shouldDriveStepBeDisabled()).to.be.true
})
it('should return false if there is a drive', function () {
const controller = $controller('MainController', {
$scope: {
$apply: _.noop
}
})
selectionState.selectImage({
path: 'rpi.img',
extension: 'img',
size: 99999,
isSizeEstimated: false
})
m.chai.expect(controller.shouldDriveStepBeDisabled()).to.be.false
})
})
describe('.shouldFlashStepBeDisabled()', function () {
it('should return true if there is no selected drive nor image', function () {
const controller = $controller('MainController', {
$scope: {
$apply: _.noop
}
})
selectionState.clear()
m.chai.expect(controller.shouldFlashStepBeDisabled()).to.be.true
})
it('should return true if there is a selected image but no drive', function () {
const controller = $controller('MainController', {
$scope: {
$apply: _.noop
}
})
selectionState.clear()
selectionState.selectImage({
path: 'rpi.img',
extension: 'img',
size: 99999,
isSizeEstimated: false
})
m.chai.expect(controller.shouldFlashStepBeDisabled()).to.be.true
})
it('should return true if there is a selected drive but no image', function () {
const controller = $controller('MainController', {
$scope: {
$apply: _.noop
}
})
availableDrives.setDrives([
{
device: '/dev/disk2',
description: 'Foo',
size: 99999,
mountpoint: '/mnt/foo',
system: false
}
])
selectionState.clear()
selectionState.selectDrive('/dev/disk2')
m.chai.expect(controller.shouldFlashStepBeDisabled()).to.be.true
})
it('should return false if there is a selected drive and a selected image', function () {
const controller = $controller('MainController', {
$scope: {
$apply: _.noop
}
})
availableDrives.setDrives([
{
device: '/dev/disk2',
description: 'Foo',
size: 99999,
mountpoint: '/mnt/foo',
system: false
}
])
selectionState.clear()
selectionState.selectDrive('/dev/disk2')
selectionState.selectImage({
path: 'rpi.img',
extension: 'img',
size: 99999,
isSizeEstimated: false
})
m.chai.expect(controller.shouldFlashStepBeDisabled()).to.be.false
})
})
})
describe('page template', function () {
let $state
beforeEach(angular.mock.inject(function (_$state_) {
$state = _$state_
}))
it('should match the file contents', function () {
const {
template
} = $state.get('main')
const contents = fs.readFileSync('lib/gui/app/pages/main/templates/main.tpl.html', {
encoding: 'utf-8'
})
m.chai.expect(template).to.equal(contents)
})
})
})