mirror of
https://github.com/balena-io/etcher.git
synced 2025-04-24 07:17:18 +00:00
Implement update notifier modal (#406)
Auto-update functionality is not ready for usage. As a workaround to prevent users staying with older versions, we now check for updates at startup, and if the user is not running the latest version, we present a modal informing the user of the availiblity of a new version, and provide a call to action to open the Etcher website in his web browser. Extra features: - The user can skip the update, and tell the program to delay the notification for 7 days. Misc changes: - Center modal with flexbox, to allow more flexibility on the modal height. interacting with the S3 server. - Implement `ManifestBindService`, which now serves as a backend for the `manifest-bind` directive to allow the directive's functionality to be re-used by other services. - Namespace checkbox styles that are specific to the settings page. Fixes: https://github.com/resin-io/etcher/issues/396 Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
This commit is contained in:
parent
662c589ab9
commit
a4e2639c00
@ -1254,7 +1254,7 @@ mark,
|
||||
.text-right, .section-header {
|
||||
text-align: right; }
|
||||
|
||||
.text-center, .alert, .alert-ribbon, .section-footer {
|
||||
.text-center, .alert, .alert-ribbon, .update-notifier-modal-body__content, .section-footer {
|
||||
text-align: center; }
|
||||
|
||||
.text-justify {
|
||||
@ -5909,9 +5909,6 @@ html {
|
||||
position: initial;
|
||||
margin-right: 2px; }
|
||||
|
||||
.checkbox input[type="checkbox"]:not(:checked) + * {
|
||||
color: #ddd; }
|
||||
|
||||
.modal-backdrop.in {
|
||||
opacity: 0; }
|
||||
|
||||
@ -6116,6 +6113,184 @@ button.btn:focus, button.progress-button:focus {
|
||||
background-color: #d9534f;
|
||||
border-color: #d9534f; }
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
.modal-content {
|
||||
display: flex;
|
||||
flex-direction: column; }
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
text-transform: uppercase;
|
||||
font-size: 11px; }
|
||||
|
||||
.modal-title {
|
||||
font-size: inherit;
|
||||
flex-grow: 1; }
|
||||
|
||||
.modal-header {
|
||||
color: #b3b3b3;
|
||||
padding: 11px 20px;
|
||||
flex-grow: 0; }
|
||||
|
||||
.modal-body {
|
||||
flex-grow: 1;
|
||||
color: #666;
|
||||
padding: 0 20px;
|
||||
max-height: 250px;
|
||||
overflow: auto; }
|
||||
|
||||
.modal-content {
|
||||
height: 320px; }
|
||||
|
||||
.modal-body .list-group-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-radius: 0;
|
||||
border-color: #eee;
|
||||
padding: 12px 0; }
|
||||
.modal-body .list-group-item > .tick {
|
||||
font-size: 11px; }
|
||||
|
||||
.modal-body .list-group-item-heading {
|
||||
font-size: 13px; }
|
||||
|
||||
.modal-body .list-group-item-text {
|
||||
line-height: 1;
|
||||
font-size: 11px;
|
||||
color: #aaa; }
|
||||
|
||||
.modal-body .list-group-item :first-child {
|
||||
flex-grow: 1; }
|
||||
|
||||
.modal-body .list-group-item:first-child {
|
||||
border-top: none; }
|
||||
|
||||
.modal-open {
|
||||
padding-right: 0 !important; }
|
||||
|
||||
.modal-fat-and-short {
|
||||
width: 400px;
|
||||
margin-top: -10px; }
|
||||
.modal-fat-and-short .modal-content {
|
||||
height: 245px; }
|
||||
|
||||
.modal-footer {
|
||||
flex-grow: 0;
|
||||
border: 0; }
|
||||
|
||||
.modal .btn-primary[disabled], .modal [disabled].progress-button--primary {
|
||||
background-color: #d5d5d5; }
|
||||
|
||||
.modal {
|
||||
display: flex !important;
|
||||
justify-content: center;
|
||||
align-items: center; }
|
||||
|
||||
.modal-dialog {
|
||||
margin: 0;
|
||||
position: initial; }
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
.alert-ribbon {
|
||||
width: 60%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
top: -100%;
|
||||
transition: top 0.5s; }
|
||||
.alert-ribbon > .glyphicon:first-child, .alert-ribbon > .tick:first-child {
|
||||
margin-right: 5px; }
|
||||
.alert-ribbon > .glyphicon:last-child, .alert-ribbon > .tick:last-child {
|
||||
margin-left: 5px; }
|
||||
.alert-ribbon .btn-link {
|
||||
padding: 0;
|
||||
font-size: inherit;
|
||||
vertical-align: baseline;
|
||||
border-radius: 0;
|
||||
border-bottom: 1px solid; }
|
||||
.alert-ribbon.alert-warning .btn-link {
|
||||
border-color: #f7dbc3;
|
||||
color: #fff; }
|
||||
.alert-ribbon.alert-warning .btn-link:hover {
|
||||
color: #e6e6e6;
|
||||
border-color: #f2c096; }
|
||||
|
||||
.alert-ribbon--open {
|
||||
top: 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.
|
||||
*/
|
||||
.update-notifier-modal-body {
|
||||
padding: 30px 35px; }
|
||||
|
||||
.update-notifier-modal-body__content {
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: 25px;
|
||||
border-bottom: 1px solid #eeeeee; }
|
||||
|
||||
.update-notifier-modal-body__title {
|
||||
margin-bottom: 15px; }
|
||||
|
||||
.update-notifier-modal-body__menu {
|
||||
display: flex;
|
||||
justify-content: center; }
|
||||
.update-notifier-modal-body__menu > .btn, .update-notifier-modal-body__menu > .progress-button {
|
||||
flex-grow: 1;
|
||||
width: 0; }
|
||||
.update-notifier-modal-body__menu > .btn + .btn, .update-notifier-modal-body__menu > .progress-button + .btn, .update-notifier-modal-body__menu > .btn + .progress-button, .update-notifier-modal-body__menu > .progress-button + .progress-button {
|
||||
margin-left: 10px; }
|
||||
|
||||
.update-notifier-modal-body .checkbox {
|
||||
color: #959595;
|
||||
font-size: 11px; }
|
||||
|
||||
/*
|
||||
* Copyright 2016 Resin.io
|
||||
*
|
||||
@ -6231,114 +6406,8 @@ button.btn:focus, button.progress-button:focus {
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
.modal-content {
|
||||
display: flex;
|
||||
flex-direction: column; }
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
text-transform: uppercase;
|
||||
font-size: 11px; }
|
||||
|
||||
.modal-title {
|
||||
font-size: inherit;
|
||||
flex-grow: 1; }
|
||||
|
||||
.modal-header {
|
||||
color: #b3b3b3;
|
||||
padding: 11px 20px;
|
||||
flex-grow: 0; }
|
||||
|
||||
.modal-body {
|
||||
flex-grow: 1;
|
||||
color: #666;
|
||||
padding: 0 20px;
|
||||
max-height: 250px;
|
||||
overflow: auto; }
|
||||
|
||||
.modal-content {
|
||||
height: 320px; }
|
||||
|
||||
.modal-body .list-group-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-radius: 0;
|
||||
border-color: #eee;
|
||||
padding: 12px 0; }
|
||||
.modal-body .list-group-item > .tick {
|
||||
font-size: 11px; }
|
||||
|
||||
.modal-body .list-group-item-heading {
|
||||
font-size: 13px; }
|
||||
|
||||
.modal-body .list-group-item-text {
|
||||
line-height: 1;
|
||||
font-size: 11px;
|
||||
color: #aaa; }
|
||||
|
||||
.modal-body .list-group-item :first-child {
|
||||
flex-grow: 1; }
|
||||
|
||||
.modal-body .list-group-item:first-child {
|
||||
border-top: none; }
|
||||
|
||||
.modal-open {
|
||||
padding-right: 0 !important; }
|
||||
|
||||
.modal-footer {
|
||||
flex-grow: 0;
|
||||
border: 0; }
|
||||
|
||||
.modal .btn-primary[disabled], .modal [disabled].progress-button--primary {
|
||||
background-color: #d5d5d5; }
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
.alert-ribbon {
|
||||
width: 60%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
top: -100%;
|
||||
transition: top 0.5s; }
|
||||
.alert-ribbon > .glyphicon:first-child, .alert-ribbon > .tick:first-child {
|
||||
margin-right: 5px; }
|
||||
.alert-ribbon > .glyphicon:last-child, .alert-ribbon > .tick:last-child {
|
||||
margin-left: 5px; }
|
||||
.alert-ribbon .btn-link {
|
||||
padding: 0;
|
||||
font-size: inherit;
|
||||
vertical-align: baseline;
|
||||
border-radius: 0;
|
||||
border-bottom: 1px solid; }
|
||||
.alert-ribbon.alert-warning .btn-link {
|
||||
border-color: #f7dbc3;
|
||||
color: #fff; }
|
||||
.alert-ribbon.alert-warning .btn-link:hover {
|
||||
color: #e6e6e6;
|
||||
border-color: #f2c096; }
|
||||
|
||||
.alert-ribbon--open {
|
||||
top: 0; }
|
||||
.page-settings .checkbox input[type="checkbox"]:not(:checked) + * {
|
||||
color: #ddd; }
|
||||
|
||||
.icon-caption {
|
||||
margin-top: 10px; }
|
||||
|
@ -40,6 +40,7 @@ const app = angular.module('Etcher', [
|
||||
require('./components/progress-button/progress-button'),
|
||||
require('./components/drive-selector/drive-selector'),
|
||||
require('./components/svg-icon/svg-icon'),
|
||||
require('./components/update-notifier/update-notifier'),
|
||||
|
||||
// Pages
|
||||
require('./pages/finish/finish'),
|
||||
@ -85,6 +86,7 @@ app.controller('AppController', function(
|
||||
ImageWriterService,
|
||||
AnalyticsService,
|
||||
DriveSelectorService,
|
||||
UpdateNotifierService,
|
||||
OSWindowProgressService,
|
||||
OSNotificationService,
|
||||
OSDialogService
|
||||
@ -96,6 +98,24 @@ app.controller('AppController', function(
|
||||
this.settings = SettingsModel.data;
|
||||
this.success = true;
|
||||
|
||||
if (UpdateNotifierService.shouldCheckForUpdates()) {
|
||||
AnalyticsService.logEvent('Checking for updates');
|
||||
|
||||
UpdateNotifierService.isLatestVersion().then(function(isLatestVersion) {
|
||||
|
||||
// In case the internet connection is not good and checking the
|
||||
// latest published version takes too long, only show notify
|
||||
// the user about the new version if he didn't start the flash
|
||||
// process (e.g: selected an image), otherwise such interruption
|
||||
// might be annoying.
|
||||
if (!isLatestVersion && !SelectionStateModel.hasImage()) {
|
||||
|
||||
AnalyticsService.logEvent('Notifying update');
|
||||
UpdateNotifierService.notify();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// This catches the case where the user enters
|
||||
// the settings screen when a flash finished
|
||||
// and goes back to the main screen with the back button.
|
||||
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.exports = function($uibModalInstance, SettingsModel) {
|
||||
|
||||
// We update this value in this controller since its the only place
|
||||
// where we can be sure the modal was really presented to the user.
|
||||
// If the controller is instantiated, means the modal was shown.
|
||||
// Compare that to `UpdateNotifierService.notify()`, which could
|
||||
// have been called, but the modal could have failed to be shown.
|
||||
SettingsModel.data.lastUpdateNotify = Date.now();
|
||||
|
||||
/**
|
||||
* @summary Settings data
|
||||
* @type Object
|
||||
* @public
|
||||
*/
|
||||
this.settings = SettingsModel.data;
|
||||
|
||||
/**
|
||||
* @summary Close the modal
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* UpdateNotifierController.closeModal();
|
||||
*/
|
||||
this.closeModal = function() {
|
||||
return $uibModalInstance.dismiss();
|
||||
};
|
||||
|
||||
};
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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 _ = require('lodash');
|
||||
const semver = require('semver');
|
||||
const xml = require('xml2js');
|
||||
|
||||
module.exports = function($q, $http, UPDATE_NOTIFIER_URL) {
|
||||
|
||||
/**
|
||||
* @summary Get the latest published Etcher version
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @description
|
||||
* This function performs its job by querying the publicily accessible
|
||||
* S3 bucket where we store the builds and uses the `node-semver` module
|
||||
* to determine which is the latest one.
|
||||
*
|
||||
* @fulfil {String} - latest version
|
||||
* @returns {Promise}
|
||||
*
|
||||
* @example
|
||||
* UpdateNotifierS3Service.getLatestVersion().then(function(latestVersion) {
|
||||
* console.log('The latest version is: ' + latestVersion);
|
||||
* });
|
||||
*/
|
||||
this.getLatestVersion = function() {
|
||||
return $http.get(UPDATE_NOTIFIER_URL).then(function(response) {
|
||||
return $q(function(resolve, reject) {
|
||||
xml.parseString(response.data, function(error, result) {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
const bucketEntries = result.ListBucketResult.Contents;
|
||||
return resolve(_.reduce(bucketEntries, function(latest, entry) {
|
||||
const version = _.chain(entry.Key)
|
||||
.first()
|
||||
.split('/')
|
||||
.nth(1)
|
||||
.value();
|
||||
|
||||
return semver.gt(version, latest) ? version : latest;
|
||||
|
||||
// This is a good accumulator default value since
|
||||
// every version is semantically greater than this.
|
||||
}, '0.0.0'));
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
};
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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 semver = require('semver');
|
||||
|
||||
module.exports = function($uibModal, UPDATE_NOTIFIER_SLEEP_TIME, ManifestBindService, UpdateNotifierS3Service, SettingsModel) {
|
||||
|
||||
/**
|
||||
* @summary Check if the current version is the latest version
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @fulfil {Boolean} - is latest version
|
||||
* @returns {Promise}
|
||||
*
|
||||
* @example
|
||||
* UpdateNotifierService.isLatestVersion().then(function(isLatestVersion) {
|
||||
* if (!isLatestVersion) {
|
||||
* console.log('There is an update available');
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
this.isLatestVersion = function() {
|
||||
return UpdateNotifierS3Service.getLatestVersion().then(function(version) {
|
||||
return semver.gte(ManifestBindService.get('version'), version);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Determine if its time to check for updates
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {Boolean} should check for updates
|
||||
*
|
||||
* @example
|
||||
* if (UpdateNotifierService.shouldCheckForUpdates()) {
|
||||
* console.log('We should check for updates!');
|
||||
* }
|
||||
*/
|
||||
this.shouldCheckForUpdates = function() {
|
||||
const lastUpdateNotify = SettingsModel.data.lastUpdateNotify;
|
||||
|
||||
if (!SettingsModel.data.sleepUpdateCheck || !lastUpdateNotify) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lastUpdateNotify - Date.now() > UPDATE_NOTIFIER_SLEEP_TIME) {
|
||||
SettingsModel.data.sleepUpdateCheck = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Open the update notifier widget
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {Promise}
|
||||
*
|
||||
* @example
|
||||
* UpdateNotifierService.notify();
|
||||
*/
|
||||
this.notify = function() {
|
||||
return $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: './components/update-notifier/templates/update-notifier-modal.tpl.html',
|
||||
controller: 'UpdateNotifierController as modal',
|
||||
size: 'fat-and-short'
|
||||
}).result;
|
||||
};
|
||||
|
||||
};
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.update-notifier-modal-body {
|
||||
padding: 30px 35px;
|
||||
}
|
||||
|
||||
.update-notifier-modal-body__content {
|
||||
@extend .text-center;
|
||||
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: 25px;
|
||||
border-bottom: 1px solid $gray-lighter;
|
||||
}
|
||||
|
||||
.update-notifier-modal-body__title {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.update-notifier-modal-body__menu {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
> .btn {
|
||||
flex-grow: 1;
|
||||
|
||||
|
||||
// This causes flex children buttons to be
|
||||
// equally resized independently of the
|
||||
// button text length
|
||||
width: 0;
|
||||
|
||||
}
|
||||
|
||||
> .btn + .btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.update-notifier-modal-body .checkbox {
|
||||
color: lighten($gray, 25%);
|
||||
font-size: 11px;
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<div class="modal-body update-notifier-modal-body">
|
||||
<div class="update-notifier-modal-body__content">
|
||||
<h4 class="update-notifier-modal-body__title">New Update Available!</h4>
|
||||
<p>A new version of Etcher is available for download</p>
|
||||
</div>
|
||||
|
||||
<div class="update-notifier-modal-body__menu">
|
||||
<button class="btn btn-primary"
|
||||
os-open-external="http://etcher.io">DOWNLOAD</button>
|
||||
<button class="btn btn-default"
|
||||
ng-click="modal.closeModal()">SKIP</button>
|
||||
</div>
|
||||
|
||||
<div class="checkbox text-right">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="modal.settings.sleepUpdateCheck">
|
||||
<span>Remind me again in 7 days</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
38
lib/gui/components/update-notifier/update-notifier.js
Normal file
38
lib/gui/components/update-notifier/update-notifier.js
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.UpdateNotifier
|
||||
*/
|
||||
|
||||
const angular = require('angular');
|
||||
const MODULE_NAME = 'Etcher.Components.UpdateNotifier';
|
||||
const UpdateNotifier = angular.module(MODULE_NAME, [
|
||||
require('angular-ui-bootstrap'),
|
||||
require('../../models/settings'),
|
||||
require('../../utils/manifest-bind/manifest-bind'),
|
||||
require('../../os/open-external/open-external')
|
||||
]);
|
||||
|
||||
UpdateNotifier.constant('UPDATE_NOTIFIER_URL', 'https://resin-production-downloads.s3.amazonaws.com');
|
||||
UpdateNotifier.constant('UPDATE_NOTIFIER_SLEEP_TIME', 7 * 24 * 60 * 60 * 100);
|
||||
UpdateNotifier.controller('UpdateNotifierController', require('./controllers/update-notifier'));
|
||||
UpdateNotifier.service('UpdateNotifierService', require('./services/update-notifier'));
|
||||
UpdateNotifier.service('UpdateNotifierS3Service', require('./services/update-notifier-s3'));
|
||||
|
||||
module.exports = MODULE_NAME;
|
@ -37,7 +37,8 @@ SettingsModel.service('SettingsModel', function($localStorage) {
|
||||
this.data = $localStorage.$default({
|
||||
errorReporting: true,
|
||||
unmountOnSuccess: true,
|
||||
validateWriteOnSuccess: true
|
||||
validateWriteOnSuccess: true,
|
||||
sleepUpdateCheck: false
|
||||
});
|
||||
|
||||
});
|
||||
|
20
lib/gui/pages/settings/styles/_settings.scss
Normal file
20
lib/gui/pages/settings/styles/_settings.scss
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.page-settings .checkbox input[type="checkbox"]:not(:checked) + * {
|
||||
color: $gray-light;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="text-left">
|
||||
<div class="page-settings text-left">
|
||||
<h1 class="space-bottom-large">Settings</h1>
|
||||
|
||||
<div class="checkbox">
|
||||
|
@ -93,6 +93,18 @@
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.modal-fat-and-short {
|
||||
width: 400px;
|
||||
|
||||
// Move it a bit to the top for
|
||||
// aesthetic reasons
|
||||
margin-top: -10px;
|
||||
|
||||
.modal-content {
|
||||
height: 245px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
flex-grow: 0;
|
||||
border: 0;
|
||||
@ -101,3 +113,18 @@
|
||||
.modal .btn-primary[disabled] {
|
||||
background-color: darken($gray-lighter, 10%);
|
||||
}
|
||||
|
||||
// Center the modal using Flexbox so we can
|
||||
// freely use any height.
|
||||
|
||||
.modal {
|
||||
display: flex !important;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
margin: 0;
|
||||
position: initial;
|
||||
}
|
||||
|
||||
|
@ -41,10 +41,12 @@ $alert-padding: 13px;
|
||||
@import "./components/caption";
|
||||
@import "./components/button";
|
||||
@import "./components/tick";
|
||||
@import "../components/progress-button/styles/progress-button";
|
||||
@import "../components/svg-icon/styles/svg-icon";
|
||||
@import "./components/modal";
|
||||
@import "./components/alert-ribbon";
|
||||
@import "../components/update-notifier/styles/update-notifier";
|
||||
@import "../components/progress-button/styles/progress-button";
|
||||
@import "../components/svg-icon/styles/svg-icon";
|
||||
@import "../pages/settings/styles/settings";
|
||||
|
||||
.icon-caption {
|
||||
@extend .caption;
|
||||
|
@ -28,10 +28,6 @@ html {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.checkbox input[type="checkbox"]:not(:checked) + * {
|
||||
color: $gray-light;
|
||||
}
|
||||
|
||||
// Disable modal opacity
|
||||
.modal-backdrop.in {
|
||||
opacity: 0;
|
||||
|
@ -16,9 +16,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const packageJSON = require('../../../../../package.json');
|
||||
|
||||
/**
|
||||
* @summary ManifestBind directive
|
||||
* @function
|
||||
@ -28,17 +25,18 @@ const packageJSON = require('../../../../../package.json');
|
||||
* This directive provides an attribute to bind the current
|
||||
* element value to a property in `package.json`.
|
||||
*
|
||||
* @param {Object} ManifestBindService - ManifestBindService
|
||||
* @returns {Object} directive
|
||||
*
|
||||
* @example
|
||||
* <span manifest-bind="version"></button>
|
||||
*/
|
||||
module.exports = function() {
|
||||
module.exports = function(ManifestBindService) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: false,
|
||||
link: function(scope, element, attributes) {
|
||||
const value = _.get(packageJSON, attributes.manifestBind);
|
||||
const value = ManifestBindService.get(attributes.manifestBind);
|
||||
|
||||
if (!value) {
|
||||
throw new Error('ManifestBind: Unknown property `' + attributes.manifestBind + '`');
|
||||
|
@ -27,6 +27,7 @@
|
||||
const angular = require('angular');
|
||||
const MODULE_NAME = 'Etcher.Utils.ManifestBind';
|
||||
const ManifestBind = angular.module(MODULE_NAME, []);
|
||||
ManifestBind.service('ManifestBindService', require('./services/manifest-bind'));
|
||||
ManifestBind.directive('manifestBind', require('./directives/manifest-bind'));
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
|
39
lib/gui/utils/manifest-bind/services/manifest-bind.js
Normal file
39
lib/gui/utils/manifest-bind/services/manifest-bind.js
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 _ = require('lodash');
|
||||
const packageJSON = require('../../../../../package.json');
|
||||
|
||||
module.exports = function() {
|
||||
|
||||
/**
|
||||
* @summary Get a package.json property
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {String} attribute - attribute
|
||||
* @returns {*} property value
|
||||
*
|
||||
* @example
|
||||
* const version = ManifestBindService.get('version');
|
||||
*/
|
||||
this.get = function(attribute) {
|
||||
return _.get(packageJSON, attribute);
|
||||
};
|
||||
|
||||
};
|
@ -65,10 +65,12 @@
|
||||
"resin-cli-errors": "^1.2.0",
|
||||
"resin-cli-form": "^1.4.1",
|
||||
"resin-cli-visuals": "^1.2.8",
|
||||
"semver": "^5.1.0",
|
||||
"sudo-prompt": "^3.1.0",
|
||||
"trackjs": "^2.1.16",
|
||||
"umount": "^1.1.3",
|
||||
"username": "^2.1.0",
|
||||
"xml2js": "^0.4.16",
|
||||
"yargs": "^4.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
325
tests/gui/components/update-notifier.spec.js
Normal file
325
tests/gui/components/update-notifier.spec.js
Normal file
@ -0,0 +1,325 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const m = require('mochainon');
|
||||
const angular = require('angular');
|
||||
require('angular-mocks');
|
||||
|
||||
describe('Browser: UpdateNotifier', function() {
|
||||
|
||||
beforeEach(angular.mock.module(
|
||||
require('../../../lib/gui/components/update-notifier/update-notifier')
|
||||
));
|
||||
|
||||
describe('UpdateNotifierService', function() {
|
||||
|
||||
describe('.shouldCheckForUpdates()', function() {
|
||||
|
||||
let UpdateNotifierService;
|
||||
let SettingsModel;
|
||||
let UPDATE_NOTIFIER_SLEEP_TIME;
|
||||
|
||||
beforeEach(angular.mock.inject(function(_UpdateNotifierService_, _SettingsModel_, _UPDATE_NOTIFIER_SLEEP_TIME_) {
|
||||
UpdateNotifierService = _UpdateNotifierService_;
|
||||
SettingsModel = _SettingsModel_;
|
||||
UPDATE_NOTIFIER_SLEEP_TIME = _UPDATE_NOTIFIER_SLEEP_TIME_;
|
||||
}));
|
||||
|
||||
describe('given the `sleepUpdateCheck` is disabled', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
SettingsModel.data.sleepUpdateCheck = false;
|
||||
});
|
||||
|
||||
it('should return true', function() {
|
||||
const result = UpdateNotifierService.shouldCheckForUpdates();
|
||||
m.chai.expect(result).to.be.true;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given the `sleepUpdateCheck` is enabled', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
SettingsModel.data.sleepUpdateCheck = true;
|
||||
});
|
||||
|
||||
describe('given the `lastUpdateNotify` was never updated', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
SettingsModel.data.lastUpdateNotify = undefined;
|
||||
});
|
||||
|
||||
it('should return true', function() {
|
||||
const result = UpdateNotifierService.shouldCheckForUpdates();
|
||||
m.chai.expect(result).to.be.true;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given the `lastUpdateNotify` was very recently updated', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
SettingsModel.data.lastUpdateNotify = Date.now() + 1000;
|
||||
});
|
||||
|
||||
it('should return false', function() {
|
||||
const result = UpdateNotifierService.shouldCheckForUpdates();
|
||||
m.chai.expect(result).to.be.false;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given the `lastUpdateNotify` was updated long ago', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
SettingsModel.data.lastUpdateNotify = Date.now() + UPDATE_NOTIFIER_SLEEP_TIME + 1;
|
||||
});
|
||||
|
||||
it('should return true', function() {
|
||||
const result = UpdateNotifierService.shouldCheckForUpdates();
|
||||
m.chai.expect(result).to.be.true;
|
||||
});
|
||||
|
||||
it('should unset the `sleepUpdateCheck` setting', function() {
|
||||
m.chai.expect(SettingsModel.data.sleepUpdateCheck).to.be.true;
|
||||
UpdateNotifierService.shouldCheckForUpdates();
|
||||
m.chai.expect(SettingsModel.data.sleepUpdateCheck).to.be.false;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.isLatestVersion()', function() {
|
||||
|
||||
describe('given the latest version is equal to the current version', function() {
|
||||
|
||||
let $q;
|
||||
let $rootScope;
|
||||
let UpdateNotifierService;
|
||||
let ManifestBindService;
|
||||
|
||||
beforeEach(function() {
|
||||
angular.mock.module(function($provide) {
|
||||
$provide.value('UpdateNotifierS3Service', {
|
||||
getLatestVersion: function() {
|
||||
return $q.resolve(ManifestBindService.get('version'));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(angular.mock.inject(function(_$q_, _$rootScope_, _UpdateNotifierService_, _ManifestBindService_) {
|
||||
$q = _$q_;
|
||||
$rootScope = _$rootScope_;
|
||||
UpdateNotifierService = _UpdateNotifierService_;
|
||||
ManifestBindService = _ManifestBindService_;
|
||||
}));
|
||||
|
||||
it('should resolve true', function() {
|
||||
let result = null;
|
||||
|
||||
UpdateNotifierService.isLatestVersion().then(function(isLatestVersion) {
|
||||
result = isLatestVersion;
|
||||
});
|
||||
|
||||
$rootScope.$apply();
|
||||
m.chai.expect(result).to.be.true;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given the latest version is greater than the current version', function() {
|
||||
|
||||
let $q;
|
||||
let $rootScope;
|
||||
let UpdateNotifierService;
|
||||
|
||||
beforeEach(function() {
|
||||
angular.mock.module(function($provide) {
|
||||
$provide.value('UpdateNotifierS3Service', {
|
||||
getLatestVersion: function() {
|
||||
return $q.resolve('99999.9.9');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(angular.mock.inject(function(_$q_, _$rootScope_, _UpdateNotifierService_) {
|
||||
$q = _$q_;
|
||||
$rootScope = _$rootScope_;
|
||||
UpdateNotifierService = _UpdateNotifierService_;
|
||||
}));
|
||||
|
||||
it('should resolve false', function() {
|
||||
let result = null;
|
||||
|
||||
UpdateNotifierService.isLatestVersion().then(function(isLatestVersion) {
|
||||
result = isLatestVersion;
|
||||
});
|
||||
|
||||
$rootScope.$apply();
|
||||
m.chai.expect(result).to.be.false;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given the latest version is less than the current version', function() {
|
||||
|
||||
let $q;
|
||||
let $rootScope;
|
||||
let UpdateNotifierService;
|
||||
|
||||
beforeEach(function() {
|
||||
angular.mock.module(function($provide) {
|
||||
$provide.value('UpdateNotifierS3Service', {
|
||||
getLatestVersion: function() {
|
||||
return $q.resolve('0.0.0');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(angular.mock.inject(function(_$q_, _$rootScope_, _UpdateNotifierService_) {
|
||||
$q = _$q_;
|
||||
$rootScope = _$rootScope_;
|
||||
UpdateNotifierService = _UpdateNotifierService_;
|
||||
}));
|
||||
|
||||
it('should resolve true', function() {
|
||||
let result = null;
|
||||
|
||||
UpdateNotifierService.isLatestVersion().then(function(isLatestVersion) {
|
||||
result = isLatestVersion;
|
||||
});
|
||||
|
||||
$rootScope.$apply();
|
||||
m.chai.expect(result).to.be.true;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('UpdateNotifierS3Service', function() {
|
||||
|
||||
let UpdateNotifierS3Service;
|
||||
let $rootScope;
|
||||
|
||||
beforeEach(angular.mock.inject(function(_$rootScope_, _UpdateNotifierS3Service_) {
|
||||
$rootScope = _$rootScope_;
|
||||
UpdateNotifierS3Service = _UpdateNotifierS3Service_;
|
||||
}));
|
||||
|
||||
describe('given a mocked S3 XML response', function() {
|
||||
|
||||
let $httpBackend;
|
||||
let UPDATE_NOTIFIER_URL;
|
||||
|
||||
beforeEach(angular.mock.inject(function($injector) {
|
||||
$httpBackend = $injector.get('$httpBackend');
|
||||
UPDATE_NOTIFIER_URL = $injector.get('UPDATE_NOTIFIER_URL');
|
||||
|
||||
$httpBackend.whenGET(UPDATE_NOTIFIER_URL).respond(`
|
||||
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Name>resin-production-downloads</Name>
|
||||
<Prefix/>
|
||||
<Marker/>
|
||||
<MaxKeys>1000</MaxKeys>
|
||||
<IsTruncated>false</IsTruncated>
|
||||
<Contents>
|
||||
<Key>etcher/1.0.0-beta.0/Etcher-darwin-x64.dmg</Key>
|
||||
<LastModified>2016-03-10T17:34:21.000Z</LastModified>
|
||||
<ETag>"5a715255aa25686688bf1e23bc1d3fc6"</ETag>
|
||||
<Size>46109720</Size>
|
||||
<StorageClass>STANDARD</StorageClass>
|
||||
</Contents>
|
||||
<Contents>
|
||||
<Key>etcher/1.0.0-beta.1/Etcher-darwin-x64.dmg</Key>
|
||||
<LastModified>2016-04-08T20:12:03.000Z</LastModified>
|
||||
<ETag>"cc1d6d9d53385e3edd099416fcd894c1"</ETag>
|
||||
<Size>47071474</Size>
|
||||
<StorageClass>STANDARD</StorageClass>
|
||||
</Contents>
|
||||
<Contents>
|
||||
<Key>etcher/1.0.0-beta.2/Etcher-darwin-x64.dmg</Key>
|
||||
<LastModified>2016-04-08T19:03:18.000Z</LastModified>
|
||||
<ETag>"5f1849f7781197ce2ee6129c16bcd498"</ETag>
|
||||
<Size>48650090</Size>
|
||||
<StorageClass>STANDARD</StorageClass>
|
||||
</Contents>
|
||||
<Contents>
|
||||
<Key>etcher/1.0.0-beta.3/Etcher-darwin-x64.dmg</Key>
|
||||
<LastModified>2016-04-18T01:32:09.000Z</LastModified>
|
||||
<ETag>"c173895886f44d115c66e7206ce3dff8"</ETag>
|
||||
<Size>50585335</Size>
|
||||
<StorageClass>STANDARD</StorageClass>
|
||||
</Contents>
|
||||
<Contents>
|
||||
<Key>etcher/1.0.0-beta.3/Etcher-darwin-x64.zip</Key>
|
||||
<LastModified>2016-04-18T01:42:37.000Z</LastModified>
|
||||
<ETag>"e9f6e957e65373b232530215d98df141"</ETag>
|
||||
<Size>129327442</Size>
|
||||
<StorageClass>STANDARD</StorageClass>
|
||||
</Contents>
|
||||
<Contents>
|
||||
<Key>etcher/1.0.0-beta.4/Etcher-darwin-x64.dmg</Key>
|
||||
<LastModified>2016-04-22T17:29:49.000Z</LastModified>
|
||||
<ETag>"bccb0024c58747a9b7516cbdfc5a7ecb"</ETag>
|
||||
<Size>55240852</Size>
|
||||
<StorageClass>STANDARD</StorageClass>
|
||||
</Contents>
|
||||
<Contents>
|
||||
<Key>etcher/1.0.0-beta.4/Etcher-darwin-x64.zip</Key>
|
||||
<LastModified>2016-04-22T17:43:27.000Z</LastModified>
|
||||
<ETag>"c93e26e68b3c4f2b7e8e88e6befc8e64"</ETag>
|
||||
<Size>135443284</Size>
|
||||
<StorageClass>STANDARD</StorageClass>
|
||||
</Contents>
|
||||
<Contents>
|
||||
<Key>etcher/1.0.0-beta.5/Etcher-darwin-x64.dmg</Key>
|
||||
<LastModified>2016-05-04T08:27:11.000Z</LastModified>
|
||||
<ETag>"fb596bfdb8bbaf09807b5fc4a940ce14"</ETag>
|
||||
<Size>77757305</Size>
|
||||
<StorageClass>STANDARD</StorageClass>
|
||||
</Contents>
|
||||
<Contents>
|
||||
<Key>etcher/1.0.0-beta.5/Etcher-darwin-x64.zip</Key>
|
||||
<LastModified>2016-05-04T08:39:56.000Z</LastModified>
|
||||
<ETag>"3f11c1b6f06644f9ceb2aea4b1947fdf"</ETag>
|
||||
<Size>157933876</Size>
|
||||
<StorageClass>STANDARD</StorageClass>
|
||||
</Contents>
|
||||
</ListBucketResult>
|
||||
`);
|
||||
}));
|
||||
|
||||
afterEach(function() {
|
||||
$httpBackend.verifyNoOutstandingExpectation();
|
||||
$httpBackend.verifyNoOutstandingRequest();
|
||||
});
|
||||
|
||||
it('should resolve the latest version', function() {
|
||||
$httpBackend.expectGET(UPDATE_NOTIFIER_URL);
|
||||
|
||||
let latestVersion = null;
|
||||
UpdateNotifierS3Service.getLatestVersion().then(function(result) {
|
||||
latestVersion = result;
|
||||
});
|
||||
|
||||
$rootScope.$apply();
|
||||
$httpBackend.flush();
|
||||
|
||||
m.chai.expect(latestVersion).to.equal('1.0.0-beta.5');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
@ -35,6 +35,31 @@ describe('Browser: ManifestBind', function() {
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
describe('ManifestBindService', function() {
|
||||
|
||||
let ManifestBindService;
|
||||
|
||||
beforeEach(angular.mock.inject(function(_ManifestBindService_) {
|
||||
ManifestBindService = _ManifestBindService_;
|
||||
}));
|
||||
|
||||
it('should be able to fetch top level properties', function() {
|
||||
const value = ManifestBindService.get('version');
|
||||
m.chai.expect(value).to.equal(packageJSON.version);
|
||||
});
|
||||
|
||||
it('should be able to fetch nested properties', function() {
|
||||
const value = ManifestBindService.get('repository.type');
|
||||
m.chai.expect(value).to.equal(packageJSON.repository.type);
|
||||
});
|
||||
|
||||
it('should return undefined if the property does not exist', function() {
|
||||
const value = ManifestBindService.get('foo.bar');
|
||||
m.chai.expect(value).to.be.undefined;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('manifestBind', function() {
|
||||
|
||||
it('should bind to top level properties', function() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user