Merge pull request #2965 from balena-io/revamp-settings

Refactor settings page into modal
This commit is contained in:
Lorenzo Alberto Maria Ambrosi 2019-12-03 11:06:43 +01:00 committed by GitHub
commit 0ab967b7a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 853 additions and 524 deletions

2
.gitattributes vendored
View File

@ -1,6 +1,8 @@
# Javascript files must retain LF line-endings (to keep eslint happy) # Javascript files must retain LF line-endings (to keep eslint happy)
*.js text eol=lf *.js text eol=lf
*.jsx text eol=lf *.jsx text eol=lf
*.ts text eol=lf
*.tsx text eol=lf
# CSS and SCSS files must retain LF line-endings (to keep ensure-staged-sass.sh happy) # CSS and SCSS files must retain LF line-endings (to keep ensure-staged-sass.sh happy)
*.css text eol=lf *.css text eol=lf
*.scss text eol=lf *.scss text eol=lf

View File

@ -94,7 +94,7 @@ const app = angular.module('Etcher', [
// Pages // Pages
require('./pages/main/main'), require('./pages/main/main'),
require('./pages/finish/finish'), require('./pages/finish/finish'),
require('./pages/settings/settings'), require('./components/settings/index.ts').MODULE_NAME,
// OS // OS
require('./os/open-external/open-external'), require('./os/open-external/open-external'),

View File

@ -189,7 +189,7 @@ const File = styled(UnstyledFile)`
display: flex; display: flex;
justify-content: center; justify-content: center;
text-align: center; text-align: center;
font-size: 14px; font-size: 16px;
} }
> div:last-child { > div:last-child {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2016 resin.io * Copyright 2019 balena.io
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,14 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
.page-settings .checkbox input[type="checkbox"] + * { /**
color: $palette-theme-dark-foreground; * @module Etcher.Components.FeaturedProject
} */
.page-settings .checkbox input[type="checkbox"]:not(:checked) + * { import * as angular from 'angular';
color: $palette-theme-dark-soft-foreground; import { react2angular } from 'react2angular';
} import { SettingsButton } from './settings';
.page-settings .title { export const MODULE_NAME = 'Etcher.Components.Settings';
color: $palette-theme-dark-foreground; const Settings = angular.module(MODULE_NAME, []);
}
Settings.component('settings', react2angular(SettingsButton));

View File

@ -0,0 +1,233 @@
/*
* 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 { faCog } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import * as _ from 'lodash';
import * as os from 'os';
import * as propTypes from 'prop-types';
import * as React from 'react';
import { Badge, Button, Checkbox, Modal, Provider } from 'rendition';
import styled from 'styled-components';
import * as settings from '../../models/settings';
import * as store from '../../models/store';
import * as analytics from '../../modules/analytics';
import { colors } from '../../theme';
const { useState } = React;
const platform = os.platform();
export const SettingsButton = () => {
const [hideModal, setHideModal] = useState(true);
return (
<Provider>
<Button
icon={<FontAwesomeIcon icon={faCog} />}
color={colors.secondary.background}
fontSize={24}
plain
onClick={() => setHideModal(false)}
tabIndex={5}
></Button>
{hideModal ? null : (
<SettingsModal toggleModal={(value: boolean) => setHideModal(!value)} />
)}
</Provider>
);
};
SettingsButton.propTypes = {};
interface WarningModalProps {
message: string;
confirmLabel: string;
cancel: () => void;
done: () => void;
}
const WarningModal = ({
message,
confirmLabel,
cancel,
done,
}: WarningModalProps) => {
return (
<Modal
title={confirmLabel}
action={confirmLabel}
cancel={cancel}
done={done}
style={{
width: 420,
height: 300,
}}
primaryButtonProps={{ warning: true }}
>
{message}
</Modal>
);
};
interface Setting {
name: string;
label: string | JSX.Element;
options?: any;
hide?: boolean;
}
const settingsList: Setting[] = [
{
name: 'errorReporting',
label: 'Anonymously report errors and usage statistics to balena.io',
},
{
name: 'unmountOnSuccess',
/**
* On Windows, "Unmounting" basically means "ejecting".
* On top of that, Windows users are usually not even
* familiar with the meaning of "unmount", which comes
* from the UNIX world.
*/
label: `${platform === 'win32' ? 'Eject' : 'Auto-unmount'} on success`,
},
{
name: 'validateWriteOnSuccess',
label: 'Validate write on success',
},
{
name: 'trim',
label: 'Trim ext{2,3,4} partitions before writing (raw images only)',
},
{
name: 'updatesEnabled',
label: 'Auto-updates enabled',
},
{
name: 'unsafeMode',
label: (
<span>
Unsafe mode{' '}
<Badge danger fontSize={12}>
Dangerous
</Badge>
</span>
),
options: {
description: `Are you sure you want to turn this on?
You will be able to overwrite your system drives if you're not careful.`,
confirmLabel: 'Enable unsafe mode',
},
hide: settings.get('disableUnsafeMode'),
},
];
interface SettingsModalProps {
toggleModal: (value: boolean) => void;
}
export const SettingsModal: any = styled(
({ toggleModal }: SettingsModalProps) => {
const [currentSettings, setCurrentSettings] = useState(settings.getAll());
const [warning, setWarning]: [
any,
React.Dispatch<React.SetStateAction<any>>,
] = useState({});
const toggleSetting = async (setting: string, options?: any) => {
const value = currentSettings[setting];
const dangerous = !_.isUndefined(options);
analytics.logEvent('Toggle setting', {
setting,
value,
dangerous,
applicationSessionUuid: store.getState().toJS().applicationSessionUuid,
});
if (value || !dangerous) {
await settings.set(setting, !value);
setCurrentSettings({
...currentSettings,
[setting]: !value,
});
setWarning({});
return;
}
// Show warning since it's a dangerous setting
setWarning({
setting,
settingValue: value,
...options,
});
};
return (
<Modal
id="settings-modal"
title="Settings"
done={() => toggleModal(false)}
style={{
width: 780,
height: 420,
}}
>
<div>
{_.map(settingsList, (setting: Setting, i: number) => {
return setting.hide ? null : (
<div key={setting.name}>
<Checkbox
toggle
tabIndex={6 + i}
label={setting.label}
checked={currentSettings[setting.name]}
onChange={() => toggleSetting(setting.name, setting.options)}
/>
</div>
);
})}
</div>
{_.isEmpty(warning) ? null : (
<WarningModal
message={warning.description}
confirmLabel={warning.confirmLabel}
done={() => {
settings.set(warning.setting, !warning.settingValue);
setCurrentSettings({
...currentSettings,
[warning.setting]: true,
});
setWarning({});
}}
cancel={() => {
setWarning({});
}}
/>
)}
</Modal>
);
},
)`
> div:nth-child(3) {
justify-content: center;
}
`;
SettingsModal.propTypes = {
toggleModal: propTypes.func,
};

View File

@ -11,57 +11,27 @@
</head> </head>
<body> <body>
<header class="section-header" ng-controller="HeaderController as header"> <header class="section-header" ng-controller="HeaderController as header">
<button class="button button-link" <span
ng-if="header.shouldShowHelp()" id="app-logo"
ng-click="header.openHelpPage()" os-open-external="https://www.balena.io/etcher?ref=etcher_footer"
tabindex="4">
<span class="glyphicon glyphicon-question-sign"></span>
</button>
<button class="button button-link"
ui-sref="settings"
hide-if-state="settings"
tabindex="5">
<span class="glyphicon glyphicon-cog"></span>
</button>
<button class="button button-link"
tabindex="5"
ui-sref="main"
show-if-state="settings">
<span class="glyphicon glyphicon-chevron-left"></span> Back
</button>
</header>
<main class="wrapper" ui-view></main>
<footer class="section-footer-main" ng-controller="StateController as state"
ng-hide="state.currentName === 'success'">
<span os-open-external="https://www.balena.io/etcher?ref=etcher_footer"
tabindex="100"> tabindex="100">
<svg-icon paths="[ '../../assets/etcher.svg' ]" <svg-icon paths="[ '../../assets/etcher.svg' ]"
width="'123px'" width="'123px'"
height="'22px'"></svg-icon> height="'22px'"></svg-icon>
</span> </span>
<span class="caption"> <settings tabindex="4">
is <span class="caption" </settings>
tabindex="101"
os-open-external="https://github.com/balena-io/etcher">an open source project</span> by
</span>
<span os-open-external="https://www.balena.io?ref=etcher" <button class="button button-link"
tabindex="102"> ng-if="header.shouldShowHelp()"
<svg-icon paths="[ '../../assets/balena.svg' ]" ng-click="header.openHelpPage()"
width="'79px'" tabindex="5">
height="'23px'"></svg-icon> <span class="glyphicon glyphicon-question-sign"></span>
</span> </button>
</header>
<span class="caption footer-right" <main class="wrapper" ui-view></main>
tabindex="103"
manifest-bind="version"
os-open-external="https://github.com/balena-io/etcher/blob/master/CHANGELOG.md"></span>
</footer>
<div class="section-loader" <div class="section-loader"
ng-controller="StateController as state" ng-controller="StateController as state"

View File

@ -120,7 +120,7 @@ svg-icon > img[disabled] {
} }
.page-main .button.step-footer { .page-main .button.step-footer {
font-size: 14px; font-size: 16px;
color: $palette-theme-primary-background; color: $palette-theme-primary-background;
border-radius: 0; border-radius: 0;
padding: 0; padding: 0;
@ -166,7 +166,7 @@ svg-icon > img[disabled] {
.page-main .step-size { .page-main .step-size {
color: $palette-theme-dark-disabled-foreground; color: $palette-theme-dark-disabled-foreground;
margin: 0 0 8px 0; margin: 0 0 8px 0;
font-size: 14px; font-size: 16px;
line-height: 1.5; line-height: 1.5;
height: 21px; height: 21px;
width: 100%; width: 100%;
@ -191,7 +191,7 @@ svg-icon > img[disabled] {
.target-status-line { .target-status-line {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
font-size: 14px; font-size: 16px;
font-family: inherit; font-family: inherit;
> .target-status-dot { > .target-status-dot {
@ -226,3 +226,17 @@ svg-icon > img[disabled] {
.space-vertical-large { .space-vertical-large {
position: relative; position: relative;
} }
body.rendition-modal-open > div:last-child > div > div > div:last-child {
top: unset;
bottom: -200px;
}
#app-logo {
position: fixed;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
width: 123px;
}

View File

@ -1,123 +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 os = require('os')
const _ = require('lodash')
const store = require('../../../models/store')
const settings = require('../../../models/settings')
const analytics = require('../../../modules/analytics')
const exceptionReporter = require('../../../modules/exception-reporter')
module.exports = function (WarningModalService) {
/**
* @summary Client platform
* @type {String}
* @constant
* @public
*/
this.platform = os.platform()
/**
* @summary Refresh current settings
* @function
* @public
*
* @example
* SettingsController.refreshSettings();
*/
this.refreshSettings = () => {
this.currentData = settings.getAll()
}
/**
* @summary Current settings value
* @type {Object}
* @public
*/
this.currentData = {}
this.refreshSettings()
/**
* @summary Settings model
* @type {Object}
* @public
*/
this.model = settings
/**
* @summary Toggle setting
* @function
* @public
*
* @description
* If warningOptions is given, it should be an object having `description` and `confirmationLabel`;
* these will be used to present a user confirmation modal before enabling the setting.
* If warningOptions is missing, no confirmation modal is displayed.
*
* @param {String} setting - setting key
* @param {Object} [options] - options
* @param {String} [options.description] - warning modal description
* @param {String} [options.confirmationLabel] - warning modal confirmation label
* @returns {Undefined}
*
* @example
* SettingsController.toggle('unsafeMode', {
* description: 'Don\'t do this!',
* confirmationLabel: 'Do it!'
* });
*/
this.toggle = (setting, options) => {
const value = this.currentData[setting]
const dangerous = !_.isUndefined(options)
analytics.logEvent('Toggle setting', {
setting,
value,
dangerous,
applicationSessionUuid: store.getState().toJS().applicationSessionUuid
})
if (!value || !dangerous) {
return this.model.set(setting, value)
}
// Keep the checkbox unchecked until the user confirms
this.currentData[setting] = false
return WarningModalService.display(options).then((userAccepted) => {
if (userAccepted) {
this.model.set(setting, true)
this.refreshSettings()
}
}).catch(exceptionReporter.report)
}
/**
* @summary Show unsafe mode based on an env var
* @function
* @public
*
* @returns {Boolean}
*
* @example
* SettingsController.shouldShowUnsafeMode()
*/
this.shouldShowUnsafeMode = () => {
return !settings.get('disableUnsafeMode')
}
}

View File

@ -1,41 +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.Pages.Settings
*/
const angular = require('angular')
const MODULE_NAME = 'Etcher.Pages.Settings'
const SettingsPage = angular.module(MODULE_NAME, [
require('angular-ui-router'),
require('../../components/warning-modal/warning-modal')
])
SettingsPage.controller('SettingsController', require('./controllers/settings'))
SettingsPage.config(($stateProvider) => {
$stateProvider
.state('settings', {
url: '/settings',
controller: 'SettingsController as settings',
template: require('./templates/settings.tpl.html')
})
})
module.exports = MODULE_NAME

View File

@ -1,80 +0,0 @@
<div class="page-settings text-left">
<h1 class="title space-bottom-large">Settings</h1>
<div class="checkbox">
<label>
<input type="checkbox"
tabindex="6"
ng-model="settings.currentData.errorReporting"
ng-change="settings.toggle('errorReporting')">
<span>Anonymously report errors and usage statistics to balena.io</span>
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox"
tabindex="7"
ng-model="settings.currentData.unmountOnSuccess"
ng-change="settings.toggle('unmountOnSuccess')">
<!-- On Windows, "Unmounting" basically means "ejecting". -->
<!-- On top of that, Windows users are usually not even -->
<!-- familiar with the meaning of "unmount", which comes -->
<!-- from the UNIX world. -->
<span>
<span ng-show="settings.platform == 'win32'">Eject</span>
<span ng-hide="settings.platform == 'win32'">Auto-unmount</span>
on success
</span>
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox"
tabindex="8"
ng-model="settings.currentData.validateWriteOnSuccess"
ng-change="settings.toggle('validateWriteOnSuccess')">
<span>Validate write on success</span>
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox"
tabindex="8"
ng-model="settings.currentData.trim"
ng-change="settings.toggle('trim')">
<span>Trim ext{2,3,4} partitions before writing (raw images only)</span>
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox"
tabindex="9"
ng-model="settings.currentData.updatesEnabled"
ng-change="settings.toggle('updatesEnabled')">
<span>Auto-updates enabled</span>
</label>
</div>
<div class="checkbox" ng-if="settings.shouldShowUnsafeMode()">
<label>
<input type="checkbox"
tabindex="10"
ng-model="settings.currentData.unsafeMode"
ng-change="settings.toggle('unsafeMode', {
description: 'Are you sure you want to turn this on? You will be able to overwrite your system drives if you\'re not careful.',
confirmationLabel: 'Enable unsafe mode'
})">
<span>Unsafe mode <span class="label label-danger">Dangerous</span></span>
</label>
</div>
</div>

View File

@ -29,8 +29,9 @@
position: relative; position: relative;
> .glyphicon { > .glyphicon {
top: 2px; top: 0;
margin-right: 2px; width: 24px;
height: 24px;
} }
&.button-primary{ &.button-primary{

View File

@ -38,7 +38,6 @@ $disabled-opacity: 0.2;
@import "../components/warning-modal/styles/warning-modal"; @import "../components/warning-modal/styles/warning-modal";
@import "../components/file-selector/styles/file-selector"; @import "../components/file-selector/styles/file-selector";
@import "../pages/main/styles/main"; @import "../pages/main/styles/main";
@import "../pages/settings/styles/settings";
@import "../pages/finish/styles/finish"; @import "../pages/finish/styles/finish";
$fa-font-path: "../../../node_modules/@fortawesome/fontawesome-free-webfonts/webfonts"; $fa-font-path: "../../../node_modules/@fortawesome/fontawesome-free-webfonts/webfonts";
@ -212,11 +211,21 @@ body {
.section-header { .section-header {
text-align: right; text-align: right;
padding: 5px 8px; padding: 13px 14px;
> .button { > .button {
padding-left: 3px; padding: 0;
padding-right: 3px;
> .glyphicon {
font-size: 24px;
}
}
> * {
display: inline-block;
vertical-align: middle;
height: 24px;
margin: 0 10px;
} }
} }

View File

@ -48,6 +48,10 @@ exports.colors = {
foreground: '#fff', foreground: '#fff',
background: '#2297de' background: '#2297de'
}, },
secondary: {
foreground: '#000',
background: '#ddd'
},
warning: { warning: {
foreground: '#fff', foreground: '#fff',
background: '#fca321' background: '#fca321'

View File

@ -6035,8 +6035,9 @@ body {
outline: none; outline: none;
position: relative; } position: relative; }
.button > .glyphicon, .button > .tick { .button > .glyphicon, .button > .tick {
top: 2px; top: 0;
margin-right: 2px; } width: 24px;
height: 24px; }
.button.button-primary { .button.button-primary {
width: 200px; width: 200px;
height: 48px; } height: 48px; }
@ -6452,7 +6453,7 @@ svg-icon > img[disabled] {
padding-bottom: 2px; } padding-bottom: 2px; }
.page-main .button.step-footer { .page-main .button.step-footer {
font-size: 14px; font-size: 16px;
color: #2297de; color: #2297de;
border-radius: 0; border-radius: 0;
padding: 0; padding: 0;
@ -6489,7 +6490,7 @@ svg-icon > img[disabled] {
.page-main .step-size { .page-main .step-size {
color: #787c7f; color: #787c7f;
margin: 0 0 8px 0; margin: 0 0 8px 0;
font-size: 14px; font-size: 16px;
line-height: 1.5; line-height: 1.5;
height: 21px; height: 21px;
width: 100%; } width: 100%; }
@ -6511,7 +6512,7 @@ svg-icon > img[disabled] {
.target-status-line { .target-status-line {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
font-size: 14px; font-size: 16px;
font-family: inherit; } font-family: inherit; }
.target-status-line > .target-status-dot { .target-status-line > .target-status-dot {
width: 12px; width: 12px;
@ -6535,29 +6536,17 @@ svg-icon > img[disabled] {
.space-vertical-large { .space-vertical-large {
position: relative; } position: relative; }
/* body.rendition-modal-open > div:last-child > div > div > div:last-child {
* Copyright 2016 resin.io top: unset;
* bottom: -200px; }
* 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.
*/
.page-settings .checkbox input[type="checkbox"] + * {
color: #fff; }
.page-settings .checkbox input[type="checkbox"]:not(:checked) + * { #app-logo {
color: #ddd; } position: fixed;
left: 0;
.page-settings .title { right: 0;
color: #fff; } margin-left: auto;
margin-right: auto;
width: 123px; }
/* /*
* Copyright 2016 resin.io * Copyright 2016 resin.io
@ -9973,10 +9962,16 @@ body {
.section-header { .section-header {
text-align: right; text-align: right;
padding: 5px 8px; } padding: 13px 14px; }
.section-header > .button { .section-header > .button {
padding-left: 3px; padding: 0; }
padding-right: 3px; } .section-header > .button > .glyphicon, .section-header > .button > .tick {
font-size: 24px; }
.section-header > * {
display: inline-block;
vertical-align: middle;
height: 24px;
margin: 0 10px; }
featured-project webview { featured-project webview {
flex: 0 1; flex: 0 1;

657
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -40,6 +40,9 @@
], ],
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free-webfonts": "^1.0.9", "@fortawesome/fontawesome-free-webfonts": "^1.0.9",
"@fortawesome/fontawesome-svg-core": "^1.2.25",
"@fortawesome/free-solid-svg-icons": "^5.11.2",
"@fortawesome/react-fontawesome": "^0.1.7",
"angular": "1.7.6", "angular": "1.7.6",
"angular-if-state": "^1.0.0", "angular-if-state": "^1.0.0",
"angular-moment": "^1.0.1", "angular-moment": "^1.0.1",
@ -68,7 +71,7 @@
"react-dom": "^16.8.5", "react-dom": "^16.8.5",
"react2angular": "^4.0.2", "react2angular": "^4.0.2",
"redux": "^3.5.2", "redux": "^3.5.2",
"rendition": "^8.7.2", "rendition": "^11.24.0",
"request": "^2.81.0", "request": "^2.81.0",
"resin-corvus": "^2.0.3", "resin-corvus": "^2.0.3",
"roboto-fontface": "^0.9.0", "roboto-fontface": "^0.9.0",

View File

@ -25,6 +25,9 @@ angularValidate.validate(
path.join(PROJECT_ROOT, 'lib', 'gui', '**/*.html') path.join(PROJECT_ROOT, 'lib', 'gui', '**/*.html')
], ],
{ {
customtags: [
'settings'
],
customattrs: [ customattrs: [
// Internal // Internal

View File

@ -1,46 +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'
const m = require('mochainon')
const fs = require('fs')
const angular = require('angular')
require('angular-mocks')
describe('Browser: SettingsPage', function () {
beforeEach(angular.mock.module(
require('../../../lib/gui/app/pages/settings/settings')
))
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('settings')
const contents = fs.readFileSync('lib/gui/app/pages/settings/templates/settings.tpl.html', {
encoding: 'utf-8'
})
m.chai.expect(template).to.equal(contents)
})
})
})

View File

@ -9,7 +9,8 @@
"moduleResolution": "node", "moduleResolution": "node",
"module": "commonjs", "module": "commonjs",
"target": "es2017", "target": "es2017",
"jsx": "react" "jsx": "react",
"allowSyntheticDefaultImports": true
}, },
"include": [ "include": [
"lib/**/*.ts", "lib/**/*.ts",