mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-25 02:31:59 -05:00
Refactor notifications service & components
issue #5409 - change persistent/passive notification status to alert/notification - replace showSuccess/Info/Warn/Error with showNotification/showAlert - fix and clean up notification/alert components
This commit is contained in:
parent
14c35ea56c
commit
7ac6ebb920
52 changed files with 797 additions and 369 deletions
|
@ -2,7 +2,7 @@ import Ember from 'ember';
|
|||
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'article',
|
||||
classNames: ['gh-alert', 'gh-alert-blue'],
|
||||
classNames: ['gh-alert'],
|
||||
classNameBindings: ['typeClass'],
|
||||
|
||||
notifications: Ember.inject.service(),
|
||||
|
@ -10,22 +10,18 @@ export default Ember.Component.extend({
|
|||
typeClass: Ember.computed(function () {
|
||||
var classes = '',
|
||||
message = this.get('message'),
|
||||
type,
|
||||
dismissible;
|
||||
type = Ember.get(message, 'type'),
|
||||
typeMapping;
|
||||
|
||||
// Check to see if we're working with a DS.Model or a plain JS object
|
||||
if (typeof message.toJSON === 'function') {
|
||||
type = message.get('type');
|
||||
dismissible = message.get('dismissible');
|
||||
} else {
|
||||
type = message.type;
|
||||
dismissible = message.dismissible;
|
||||
}
|
||||
typeMapping = {
|
||||
success: 'green',
|
||||
error: 'red',
|
||||
warn: 'yellow',
|
||||
info: 'blue'
|
||||
};
|
||||
|
||||
classes += 'notification-' + type;
|
||||
|
||||
if (type === 'success' && dismissible !== false) {
|
||||
classes += ' notification-passive';
|
||||
if (typeMapping[type] !== undefined) {
|
||||
classes += 'gh-alert-' + typeMapping[type];
|
||||
}
|
||||
|
||||
return classes;
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
import Ember from 'ember';
|
||||
var AlertsComponent = Ember.Component.extend({
|
||||
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'aside',
|
||||
classNames: 'gh-alerts',
|
||||
|
||||
messages: Ember.computed.filter('notifications', function (notification) {
|
||||
var displayStatus = (typeof notification.toJSON === 'function') ?
|
||||
notification.get('status') : notification.status;
|
||||
notifications: Ember.inject.service(),
|
||||
|
||||
return displayStatus === 'persistent';
|
||||
}),
|
||||
messages: Ember.computed.alias('notifications.alerts'),
|
||||
|
||||
messageCountObserver: Ember.observer('messages.[]', function () {
|
||||
this.sendAction('notify', this.get('messages').length);
|
||||
})
|
||||
});
|
||||
|
||||
export default AlertsComponent;
|
||||
|
|
|
@ -2,7 +2,7 @@ import Ember from 'ember';
|
|||
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'article',
|
||||
classNames: ['gh-notification', 'gh-notification-green'],
|
||||
classNames: ['gh-notification', 'gh-notification-passive'],
|
||||
classNameBindings: ['typeClass'],
|
||||
|
||||
message: null,
|
||||
|
@ -12,22 +12,17 @@ export default Ember.Component.extend({
|
|||
typeClass: Ember.computed(function () {
|
||||
var classes = '',
|
||||
message = this.get('message'),
|
||||
type,
|
||||
dismissible;
|
||||
type = Ember.get(message, 'type'),
|
||||
typeMapping;
|
||||
|
||||
// Check to see if we're working with a DS.Model or a plain JS object
|
||||
if (typeof message.toJSON === 'function') {
|
||||
type = message.get('type');
|
||||
dismissible = message.get('dismissible');
|
||||
} else {
|
||||
type = message.type;
|
||||
dismissible = message.dismissible;
|
||||
}
|
||||
typeMapping = {
|
||||
success: 'green',
|
||||
error: 'red',
|
||||
warn: 'yellow'
|
||||
};
|
||||
|
||||
classes += 'notification-' + type;
|
||||
|
||||
if (type === 'success' && dismissible !== false) {
|
||||
classes += ' notification-passive';
|
||||
if (typeMapping[type] !== undefined) {
|
||||
classes += 'gh-notification-' + typeMapping[type];
|
||||
}
|
||||
|
||||
return classes;
|
||||
|
@ -38,11 +33,15 @@ export default Ember.Component.extend({
|
|||
|
||||
self.$().on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', function (event) {
|
||||
if (event.originalEvent.animationName === 'fade-out') {
|
||||
self.get('notifications').removeObject(self.get('message'));
|
||||
self.get('notifications').closeNotification(self.get('message'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
willDestroyElement: function () {
|
||||
this.$().off('animationend webkitAnimationEnd oanimationend MSAnimationEnd');
|
||||
},
|
||||
|
||||
actions: {
|
||||
closeNotification: function () {
|
||||
this.get('notifications').closeNotification(this.get('message'));
|
||||
|
|
|
@ -6,10 +6,5 @@ export default Ember.Component.extend({
|
|||
|
||||
notifications: Ember.inject.service(),
|
||||
|
||||
messages: Ember.computed.filter('notifications.content', function (notification) {
|
||||
var displayStatus = (typeof notification.toJSON === 'function') ?
|
||||
notification.get('status') : notification.status;
|
||||
|
||||
return displayStatus === 'passive';
|
||||
})
|
||||
messages: Ember.computed.alias('notifications.notifications')
|
||||
});
|
||||
|
|
|
@ -24,10 +24,10 @@ export default Ember.Component.extend({
|
|||
// If sending the invitation email fails, the API will still return a status of 201
|
||||
// but the user's status in the response object will be 'invited-pending'.
|
||||
if (result.users[0].status === 'invited-pending') {
|
||||
notifications.showWarn('Invitation email was not sent. Please try resending.');
|
||||
notifications.showAlert('Invitation email was not sent. Please try resending.', {type: 'error'});
|
||||
} else {
|
||||
user.set('status', result.users[0].status);
|
||||
notifications.showSuccess(notificationText);
|
||||
notifications.showNotification(notificationText);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
notifications.showAPIError(error);
|
||||
|
@ -46,14 +46,14 @@ export default Ember.Component.extend({
|
|||
user.destroyRecord().then(function () {
|
||||
var notificationText = 'Invitation revoked. (' + email + ')';
|
||||
|
||||
notifications.showSuccess(notificationText, false);
|
||||
notifications.showNotification(notificationText);
|
||||
}).catch(function (error) {
|
||||
notifications.showAPIError(error);
|
||||
});
|
||||
} else {
|
||||
// if the user is no longer marked as "invited", then show a warning and reload the route
|
||||
self.sendAction('reload');
|
||||
notifications.showError('This user has already accepted the invitation.', {delayed: 500});
|
||||
notifications.showAlert('This user has already accepted the invitation.', {type: 'error', delayed: true});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ export default Ember.Controller.extend({
|
|||
ajax(this.get('ghostPaths.url').api('db'), {
|
||||
type: 'DELETE'
|
||||
}).then(function () {
|
||||
self.get('notifications').showSuccess('All content deleted from database.');
|
||||
self.get('notifications').showAlert('All content deleted from database.', {type: 'success'});
|
||||
self.store.unloadAll('post');
|
||||
self.store.unloadAll('tag');
|
||||
}).catch(function (response) {
|
||||
|
|
|
@ -15,9 +15,8 @@ export default Ember.Controller.extend({
|
|||
model.destroyRecord().then(function () {
|
||||
self.get('dropdown').closeDropdowns();
|
||||
self.transitionToRoute('posts.index');
|
||||
self.get('notifications').showSuccess('Your post has been deleted.', {delayed: true});
|
||||
}, function () {
|
||||
self.get('notifications').showError('Your post could not be deleted. Please try again.');
|
||||
self.get('notifications').showAlert('Your post could not be deleted. Please try again.', {type: 'error'});
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -10,14 +10,11 @@ export default Ember.Controller.extend({
|
|||
actions: {
|
||||
confirmAccept: function () {
|
||||
var tag = this.get('model'),
|
||||
name = tag.get('name'),
|
||||
self = this;
|
||||
|
||||
this.send('closeMenus');
|
||||
|
||||
tag.destroyRecord().then(function () {
|
||||
self.get('notifications').showSuccess('Deleted ' + name);
|
||||
}).catch(function (error) {
|
||||
tag.destroyRecord().catch(function (error) {
|
||||
self.get('notifications').showAPIError(error);
|
||||
});
|
||||
},
|
||||
|
|
|
@ -31,9 +31,8 @@ export default Ember.Controller.extend({
|
|||
user.destroyRecord().then(function () {
|
||||
self.store.unloadAll('post');
|
||||
self.transitionToRoute('team');
|
||||
self.get('notifications').showSuccess('The user has been deleted.', {delayed: true});
|
||||
}, function () {
|
||||
self.get('notifications').showError('The user could not be deleted. Please try again.');
|
||||
self.get('notifications').showAlert('The user could not be deleted. Please try again.', {type: 'error'});
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -55,9 +55,9 @@ export default Ember.Controller.extend({
|
|||
|
||||
if (invitedUser) {
|
||||
if (invitedUser.get('status') === 'invited' || invitedUser.get('status') === 'invited-pending') {
|
||||
self.get('notifications').showWarn('A user with that email address was already invited.');
|
||||
self.get('notifications').showAlert('A user with that email address was already invited.', {type: 'warn'});
|
||||
} else {
|
||||
self.get('notifications').showWarn('A user with that email address already exists.');
|
||||
self.get('notifications').showAlert('A user with that email address already exists.', {type: 'warn'});
|
||||
}
|
||||
} else {
|
||||
newUser = self.store.createRecord('user', {
|
||||
|
@ -72,9 +72,9 @@ export default Ember.Controller.extend({
|
|||
// If sending the invitation email fails, the API will still return a status of 201
|
||||
// but the user's status in the response object will be 'invited-pending'.
|
||||
if (newUser.get('status') === 'invited-pending') {
|
||||
self.get('notifications').showWarn('Invitation email was not sent. Please try resending.');
|
||||
self.get('notifications').showAlert('Invitation email was not sent. Please try resending.', {type: 'error'});
|
||||
} else {
|
||||
self.get('notifications').showSuccess(notificationText);
|
||||
self.get('notifications').showAlert(notificationText, {type: 'success'});
|
||||
}
|
||||
}).catch(function (errors) {
|
||||
newUser.deleteRecord();
|
||||
|
|
|
@ -19,7 +19,7 @@ export default Ember.Controller.extend({
|
|||
}
|
||||
|
||||
if (!transition || !editorController) {
|
||||
this.get('notifications').showError('Sorry, there was an error in the application. Please let the Ghost team know what happened.');
|
||||
this.get('notifications').showNotification('Sorry, there was an error in the application. Please let the Ghost team know what happened.', {type: 'error'});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ export default Ember.Controller.extend(ValidationEngine, {
|
|||
|
||||
this.get('session').authenticate(authStrategy, data).then(function () {
|
||||
self.send('closeModal');
|
||||
self.get('notifications').showSuccess('Login successful.');
|
||||
self.set('password', '');
|
||||
}).catch(function () {
|
||||
// if authentication fails a rejected promise will be returned.
|
||||
|
@ -41,7 +40,7 @@ export default Ember.Controller.extend(ValidationEngine, {
|
|||
$('#login').find('input').trigger('change');
|
||||
|
||||
this.validate({format: false}).then(function () {
|
||||
self.get('notifications').closePassive();
|
||||
self.get('notifications').closeNotifications();
|
||||
self.send('authenticate');
|
||||
}).catch(function (errors) {
|
||||
self.get('notifications').showErrors(errors);
|
||||
|
|
|
@ -33,7 +33,7 @@ export default Ember.Controller.extend({
|
|||
});
|
||||
}
|
||||
|
||||
self.get('notifications').showSuccess('Ownership successfully transferred to ' + user.get('name'));
|
||||
self.get('notifications').showAlert('Ownership successfully transferred to ' + user.get('name'), {type: 'success'});
|
||||
}).catch(function (error) {
|
||||
self.get('notifications').showAPIError(error);
|
||||
});
|
||||
|
|
|
@ -10,8 +10,6 @@ export default Ember.Controller.extend({
|
|||
var notifications = this.get('notifications');
|
||||
|
||||
this.get('model').save().then(function (model) {
|
||||
notifications.showSuccess('Saved');
|
||||
|
||||
return model;
|
||||
}).catch(function (err) {
|
||||
notifications.showErrors(err);
|
||||
|
|
|
@ -193,10 +193,6 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
|
|||
this.get('notifications').showErrors(errors);
|
||||
},
|
||||
|
||||
showSuccess: function (message) {
|
||||
this.get('notifications').showSuccess(message);
|
||||
},
|
||||
|
||||
actions: {
|
||||
togglePage: function () {
|
||||
var self = this;
|
||||
|
|
|
@ -43,7 +43,7 @@ export default Ember.Controller.extend(ValidationEngine, {
|
|||
}
|
||||
}).then(function (resp) {
|
||||
self.toggleProperty('submitting');
|
||||
self.get('notifications').showSuccess(resp.passwordreset[0].message, true);
|
||||
self.get('notifications').showAlert(resp.passwordreset[0].message, {type: 'warn', delayed: true});
|
||||
self.get('session').authenticate('simple-auth-authenticator:oauth2-password-grant', {
|
||||
identification: self.get('email'),
|
||||
password: credentials.newPassword
|
||||
|
|
|
@ -8,12 +8,11 @@ export default Ember.Controller.extend({
|
|||
var notifications = this.get('notifications');
|
||||
|
||||
return this.get('model').save().then(function (model) {
|
||||
notifications.closePassive();
|
||||
notifications.showSuccess('Settings successfully saved.');
|
||||
notifications.closeNotifications();
|
||||
|
||||
return model;
|
||||
}).catch(function (errors) {
|
||||
notifications.closePassive();
|
||||
notifications.closeNotifications();
|
||||
notifications.showErrors(errors);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -69,7 +69,6 @@ export default Ember.Controller.extend({
|
|||
|
||||
return this.get('model').save().then(function (model) {
|
||||
config.set('blogTitle', model.get('title'));
|
||||
notifications.showSuccess('Settings successfully saved.');
|
||||
|
||||
return model;
|
||||
}).catch(function (errors) {
|
||||
|
|
|
@ -36,7 +36,7 @@ export default Ember.Controller.extend({
|
|||
|
||||
this.set('uploadButtonText', 'Importing');
|
||||
this.set('importErrors', '');
|
||||
notifications.closePassive();
|
||||
notifications.closeNotifications();
|
||||
|
||||
formData.append('importfile', file);
|
||||
|
||||
|
@ -52,13 +52,14 @@ export default Ember.Controller.extend({
|
|||
self.store.unloadAll();
|
||||
// Reload currentUser and set session
|
||||
self.set('session.user', self.store.find('user', currentUserId));
|
||||
notifications.showSuccess('Import successful.');
|
||||
// TODO: keep as notification, add link to view content
|
||||
notifications.showNotification('Import successful.');
|
||||
}).catch(function (response) {
|
||||
if (response && response.jqXHR && response.jqXHR.responseJSON && response.jqXHR.responseJSON.errors) {
|
||||
self.set('importErrors', response.jqXHR.responseJSON.errors);
|
||||
}
|
||||
|
||||
notifications.showError('Import Failed');
|
||||
notifications.showAlert('Import Failed', {type: 'error'});
|
||||
}).finally(function () {
|
||||
self.set('uploadButtonText', 'Import');
|
||||
});
|
||||
|
@ -82,7 +83,7 @@ export default Ember.Controller.extend({
|
|||
ajax(this.get('ghostPaths.url').api('mail', 'test'), {
|
||||
type: 'POST'
|
||||
}).then(function () {
|
||||
notifications.showSuccess('Check your email for the test message.');
|
||||
notifications.showAlert('Check your email for the test message.', {type: 'info'});
|
||||
}).catch(function (error) {
|
||||
if (typeof error.jqXHR !== 'undefined') {
|
||||
notifications.showAPIError(error);
|
||||
|
|
|
@ -108,7 +108,7 @@ export default Ember.Controller.extend({
|
|||
|
||||
// Don't save if there's a blank label.
|
||||
if (navItems.find(function (item) {return !item.get('isComplete') && !item.get('last');})) {
|
||||
notifications.showErrors([message.htmlSafe()]);
|
||||
notifications.showAlert(message.htmlSafe(), {type: 'error'});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -148,11 +148,9 @@ export default Ember.Controller.extend({
|
|||
// we need to have navigationItems recomputed.
|
||||
this.get('model').notifyPropertyChange('navigation');
|
||||
|
||||
notifications.closePassive();
|
||||
notifications.closeNotifications();
|
||||
|
||||
this.get('model').save().then(function () {
|
||||
notifications.showSuccess('Navigation items saved.');
|
||||
}).catch(function (err) {
|
||||
this.get('model').save().catch(function (err) {
|
||||
notifications.showErrors(err);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ export default Ember.Controller.extend(PaginationMixin, SettingsMenuMixin, {
|
|||
|
||||
activeTag.set(propKey, newValue);
|
||||
|
||||
this.get('notifications').closePassive();
|
||||
this.get('notifications').closeNotifications();
|
||||
|
||||
activeTag.save().catch(function (errors) {
|
||||
self.showErrors(errors);
|
||||
|
|
|
@ -104,20 +104,21 @@ export default Ember.Controller.extend({
|
|||
if (erroredEmails.length > 0) {
|
||||
message = 'Failed to send ' + erroredEmails.length + ' invitations: ';
|
||||
message += erroredEmails.join(', ');
|
||||
notifications.showError(message, {delayed: successCount > 0});
|
||||
notifications.showAlert(message, {type: 'error', delayed: successCount > 0});
|
||||
}
|
||||
|
||||
if (successCount > 0) {
|
||||
// pluralize
|
||||
invitationsString = successCount > 1 ? 'invitations' : 'invitation';
|
||||
|
||||
notifications.showSuccess(successCount + ' ' + invitationsString + ' sent!', {delayed: true});
|
||||
notifications.showAlert(successCount + ' ' + invitationsString + ' sent!', {type: 'success', delayed: true});
|
||||
self.transitionTo('posts.index');
|
||||
}
|
||||
});
|
||||
});
|
||||
} else if (users.length === 0) {
|
||||
notifications.showError('No users to invite.');
|
||||
// TODO: switch to inline-validation
|
||||
notifications.showAlert('No users to invite.', {type: 'error'});
|
||||
} else {
|
||||
errorMessages = validationErrors.map(function (error) {
|
||||
// Only one error type here so far, but one day the errors might be more detailed
|
||||
|
@ -127,6 +128,7 @@ export default Ember.Controller.extend({
|
|||
}
|
||||
});
|
||||
|
||||
// TODO: switch to inline-validation
|
||||
notifications.showErrors(errorMessages);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ export default Ember.Controller.extend(ValidationEngine, {
|
|||
$('#login').find('input').trigger('change');
|
||||
|
||||
this.validate({format: false}).then(function () {
|
||||
self.get('notifications').closePassive();
|
||||
self.get('notifications').closeNotifications();
|
||||
self.send('authenticate');
|
||||
}).catch(function (errors) {
|
||||
if (errors) {
|
||||
|
@ -46,7 +46,8 @@ export default Ember.Controller.extend(ValidationEngine, {
|
|||
self = this;
|
||||
|
||||
if (!email) {
|
||||
return notifications.showError('Enter email address to reset password.');
|
||||
// TODO: Switch to in-line validation
|
||||
return notifications.showNotification('Enter email address to reset password.', {type: 'error'});
|
||||
}
|
||||
|
||||
self.set('submitting', true);
|
||||
|
@ -61,7 +62,7 @@ export default Ember.Controller.extend(ValidationEngine, {
|
|||
}
|
||||
}).then(function () {
|
||||
self.set('submitting', false);
|
||||
notifications.showSuccess('Please check your email for instructions.');
|
||||
notifications.showAlert('Please check your email for instructions.', {type: 'info'});
|
||||
}).catch(function (resp) {
|
||||
self.set('submitting', false);
|
||||
notifications.showAPIError(resp, {defaultErrorText: 'There was a problem with the reset, please try again.'});
|
||||
|
|
|
@ -18,7 +18,7 @@ export default Ember.Controller.extend(ValidationEngine, {
|
|||
data = model.getProperties('name', 'email', 'password', 'token'),
|
||||
notifications = this.get('notifications');
|
||||
|
||||
notifications.closePassive();
|
||||
notifications.closeNotifications();
|
||||
|
||||
this.toggleProperty('submitting');
|
||||
this.validate({format: false}).then(function () {
|
||||
|
|
|
@ -105,8 +105,6 @@ export default Ember.Controller.extend({
|
|||
var currentPath,
|
||||
newPath;
|
||||
|
||||
self.get('notifications').showSuccess('Settings successfully saved.');
|
||||
|
||||
// If the user's slug has changed, change the URL and replace
|
||||
// the history so refresh and back button still work
|
||||
if (slugChanged) {
|
||||
|
@ -142,13 +140,14 @@ export default Ember.Controller.extend({
|
|||
ne2Password: ''
|
||||
});
|
||||
|
||||
self.get('notifications').showSuccess('Password updated.');
|
||||
self.get('notifications').showAlert('Password updated.', {type: 'success'});
|
||||
|
||||
return model;
|
||||
}).catch(function (errors) {
|
||||
self.get('notifications').showAPIError(errors);
|
||||
});
|
||||
} else {
|
||||
// TODO: switch to in-line validation
|
||||
self.get('notifications').showErrors(user.get('passwordValidationErrors'));
|
||||
}
|
||||
},
|
||||
|
|
|
@ -209,6 +209,7 @@ export default Ember.Mixin.create({
|
|||
}
|
||||
},
|
||||
|
||||
// TODO: Update for new notification click-action API
|
||||
showSaveNotification: function (prevStatus, status, delay) {
|
||||
var message = this.messageMap.success.post[prevStatus][status],
|
||||
path = this.get('model.absoluteUrl'),
|
||||
|
@ -219,7 +220,7 @@ export default Ember.Mixin.create({
|
|||
message += ` <a href="${path}">View ${type}</a>`;
|
||||
}
|
||||
|
||||
notifications.showSuccess(message.htmlSafe(), {delayed: delay});
|
||||
notifications.showNotification(message.htmlSafe(), {delayed: delay});
|
||||
},
|
||||
|
||||
showErrorNotification: function (prevStatus, status, errors, delay) {
|
||||
|
@ -229,7 +230,7 @@ export default Ember.Mixin.create({
|
|||
|
||||
message += '<br />' + error;
|
||||
|
||||
notifications.showError(message.htmlSafe(), {delayed: delay});
|
||||
notifications.showAlert(message.htmlSafe(), {type: 'error', delayed: delay});
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
@ -263,7 +264,7 @@ export default Ember.Mixin.create({
|
|||
this.set('timedSaveId', null);
|
||||
}
|
||||
|
||||
notifications.closePassive();
|
||||
notifications.closeNotifications();
|
||||
|
||||
// Set the properties that are indirected
|
||||
// set markdown equal to what's in the editor, minus the image markers.
|
||||
|
|
|
@ -25,7 +25,7 @@ export default Ember.Mixin.create({
|
|||
message += '.';
|
||||
}
|
||||
|
||||
this.get('notifications').showError(message);
|
||||
this.get('notifications').showAlert(message, {type: 'error'});
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import DS from 'ember-data';
|
||||
var Notification = DS.Model.extend({
|
||||
dismissible: DS.attr('boolean'),
|
||||
location: DS.attr('string'),
|
||||
status: DS.attr('string'),
|
||||
type: DS.attr('string'),
|
||||
message: DS.attr('string')
|
||||
|
|
|
@ -11,7 +11,7 @@ var Router = Ember.Router.extend({
|
|||
clearNotifications: Ember.on('didTransition', function () {
|
||||
var notifications = this.get('notifications');
|
||||
|
||||
notifications.closePassive();
|
||||
notifications.closeNotifications();
|
||||
notifications.displayDelayed();
|
||||
})
|
||||
});
|
||||
|
|
|
@ -66,7 +66,7 @@ export default Ember.Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
|
|||
this.get('notifications').showErrors(error.errors);
|
||||
} else {
|
||||
// connection errors don't return proper status message, only req.body
|
||||
this.get('notifications').showError('There was a problem on the server.');
|
||||
this.get('notifications').showAlert('There was a problem on the server.', {type: 'error'});
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -91,7 +91,7 @@ export default Ember.Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
|
|||
},
|
||||
|
||||
sessionInvalidationFailed: function (error) {
|
||||
this.get('notifications').showError(error.message);
|
||||
this.get('notifications').showAlert(error.message, {type: 'error'});
|
||||
},
|
||||
|
||||
openModal: function (modalName, model, type) {
|
||||
|
@ -152,19 +152,6 @@ export default Ember.Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
|
|||
}
|
||||
},
|
||||
|
||||
handleErrors: function (errors) {
|
||||
var notifications = this.get('notifications');
|
||||
|
||||
notifications.clear();
|
||||
errors.forEach(function (errorObj) {
|
||||
notifications.showError(errorObj.message || errorObj);
|
||||
|
||||
if (errorObj.hasOwnProperty('el')) {
|
||||
errorObj.el.addClass('input-error');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// noop default for unhandled save (used from shortcuts)
|
||||
save: Ember.K
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ export default Ember.Route.extend(styleBody, {
|
|||
|
||||
beforeModel: function () {
|
||||
if (this.get('session').isAuthenticated) {
|
||||
this.get('notifications').showWarn('You can\'t reset your password while you\'re signed in.', {delayed: true});
|
||||
this.get('notifications').showAlert('You can\'t reset your password while you\'re signed in.', {type: 'warn', delayed: true});
|
||||
this.transitionTo(Configuration.routeAfterAuthentication);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -12,7 +12,7 @@ export default Ember.Route.extend(styleBody, {
|
|||
|
||||
beforeModel: function () {
|
||||
if (this.get('session').isAuthenticated) {
|
||||
this.get('notifications').showWarn('You need to sign out to register as a new user.', {delayed: true});
|
||||
this.get('notifications').showAlert('You need to sign out to register as a new user.', {type: 'warn', delayed: true});
|
||||
this.transitionTo(Configuration.routeAfterAuthentication);
|
||||
}
|
||||
},
|
||||
|
@ -26,7 +26,7 @@ export default Ember.Route.extend(styleBody, {
|
|||
|
||||
return new Ember.RSVP.Promise(function (resolve) {
|
||||
if (!re.test(params.token)) {
|
||||
self.get('notifications').showError('Invalid token.', {delayed: true});
|
||||
self.get('notifications').showAlert('Invalid token.', {type: 'error', delayed: true});
|
||||
|
||||
return resolve(self.transitionTo('signin'));
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ export default Ember.Route.extend(styleBody, {
|
|||
}
|
||||
}).then(function (response) {
|
||||
if (response && response.invitation && response.invitation[0].valid === false) {
|
||||
self.get('notifications').showError('The invitation does not exist or is no longer valid.', {delayed: true});
|
||||
self.get('notifications').showAlert('The invitation does not exist or is no longer valid.', {type: 'warn', delayed: true});
|
||||
|
||||
return resolve(self.transitionTo('signin'));
|
||||
}
|
||||
|
|
|
@ -1,138 +1,94 @@
|
|||
import Ember from 'ember';
|
||||
import Notification from 'ghost/models/notification';
|
||||
|
||||
export default Ember.Service.extend({
|
||||
delayedNotifications: Ember.A(),
|
||||
content: Ember.A(),
|
||||
timeout: 3000,
|
||||
|
||||
pushObject: function (object) {
|
||||
// object can be either a DS.Model or a plain JS object, so when working with
|
||||
// it, we need to handle both cases.
|
||||
alerts: Ember.computed.filter('content', function (notification) {
|
||||
var status = Ember.get(notification, 'status');
|
||||
return status === 'alert';
|
||||
}),
|
||||
|
||||
// make sure notifications have all the necessary properties set.
|
||||
if (typeof object.toJSON === 'function') {
|
||||
// working with a DS.Model
|
||||
|
||||
if (object.get('location') === '') {
|
||||
object.set('location', 'bottom');
|
||||
}
|
||||
} else {
|
||||
if (!object.location) {
|
||||
object.location = 'bottom';
|
||||
}
|
||||
}
|
||||
|
||||
this._super(object);
|
||||
},
|
||||
notifications: Ember.computed.filter('content', function (notification) {
|
||||
var status = Ember.get(notification, 'status');
|
||||
return status === 'notification';
|
||||
}),
|
||||
|
||||
handleNotification: function (message, delayed) {
|
||||
if (typeof message.toJSON === 'function') {
|
||||
// If this is a persistent message from the server, treat it as html safe
|
||||
if (message.get('status') === 'persistent') {
|
||||
message.set('message', message.get('message').htmlSafe());
|
||||
}
|
||||
// If this is an alert message from the server, treat it as html safe
|
||||
if (typeof message.toJSON === 'function' && message.get('status') === 'alert') {
|
||||
message.set('message', message.get('message').htmlSafe());
|
||||
}
|
||||
|
||||
if (!message.get('status')) {
|
||||
message.set('status', 'passive');
|
||||
}
|
||||
} else {
|
||||
if (!message.status) {
|
||||
message.status = 'passive';
|
||||
}
|
||||
if (!Ember.get(message, 'status')) {
|
||||
Ember.set(message, 'status', 'notification');
|
||||
}
|
||||
|
||||
if (!delayed) {
|
||||
this.get('content').pushObject(message);
|
||||
} else {
|
||||
this.delayedNotifications.pushObject(message);
|
||||
this.get('delayedNotifications').pushObject(message);
|
||||
}
|
||||
},
|
||||
|
||||
showError: function (message, options) {
|
||||
showAlert: function (message, options) {
|
||||
options = options || {};
|
||||
|
||||
if (!options.doNotClosePassive) {
|
||||
this.closePassive();
|
||||
}
|
||||
|
||||
this.handleNotification({
|
||||
type: 'error',
|
||||
message: message
|
||||
message: message,
|
||||
status: 'alert',
|
||||
type: options.type
|
||||
}, options.delayed);
|
||||
},
|
||||
|
||||
showNotification: function (message, options) {
|
||||
options = options || {};
|
||||
|
||||
if (!options.doNotCloseNotifications) {
|
||||
this.closeNotifications();
|
||||
}
|
||||
|
||||
this.handleNotification({
|
||||
message: message,
|
||||
status: 'notification',
|
||||
type: options.type
|
||||
}, options.delayed);
|
||||
},
|
||||
|
||||
// TODO: review whether this can be removed once no longer used by validations
|
||||
showErrors: function (errors, options) {
|
||||
options = options || {};
|
||||
|
||||
if (!options.doNotClosePassive) {
|
||||
this.closePassive();
|
||||
if (!options.doNotCloseNotifications) {
|
||||
this.closeNotifications();
|
||||
}
|
||||
|
||||
for (var i = 0; i < errors.length; i += 1) {
|
||||
this.showError(errors[i].message || errors[i], {doNotClosePassive: true});
|
||||
this.showNotification(errors[i].message || errors[i], {type: 'error', doNotCloseNotifications: true});
|
||||
}
|
||||
},
|
||||
|
||||
showAPIError: function (resp, options) {
|
||||
options = options || {};
|
||||
options.type = options.type || 'error';
|
||||
|
||||
if (!options.doNotClosePassive) {
|
||||
this.closePassive();
|
||||
if (!options.doNotCloseNotifications) {
|
||||
this.closeNotifications();
|
||||
}
|
||||
|
||||
options.defaultErrorText = options.defaultErrorText || 'There was a problem on the server, please try again.';
|
||||
|
||||
if (resp && resp.jqXHR && resp.jqXHR.responseJSON && resp.jqXHR.responseJSON.error) {
|
||||
this.showError(resp.jqXHR.responseJSON.error, options);
|
||||
this.showAlert(resp.jqXHR.responseJSON.error, options);
|
||||
} else if (resp && resp.jqXHR && resp.jqXHR.responseJSON && resp.jqXHR.responseJSON.errors) {
|
||||
this.showErrors(resp.jqXHR.responseJSON.errors, options);
|
||||
} else if (resp && resp.jqXHR && resp.jqXHR.responseJSON && resp.jqXHR.responseJSON.message) {
|
||||
this.showError(resp.jqXHR.responseJSON.message, options);
|
||||
this.showAlert(resp.jqXHR.responseJSON.message, options);
|
||||
} else {
|
||||
this.showError(options.defaultErrorText, {doNotClosePassive: true});
|
||||
this.showAlert(options.defaultErrorText, {type: options.type, doNotCloseNotifications: true});
|
||||
}
|
||||
},
|
||||
|
||||
showInfo: function (message, options) {
|
||||
options = options || {};
|
||||
|
||||
if (!options.doNotClosePassive) {
|
||||
this.closePassive();
|
||||
}
|
||||
|
||||
this.handleNotification({
|
||||
type: 'info',
|
||||
message: message
|
||||
}, options.delayed);
|
||||
},
|
||||
|
||||
showSuccess: function (message, options) {
|
||||
options = options || {};
|
||||
|
||||
if (!options.doNotClosePassive) {
|
||||
this.closePassive();
|
||||
}
|
||||
|
||||
this.handleNotification({
|
||||
type: 'success',
|
||||
message: message
|
||||
}, options.delayed);
|
||||
},
|
||||
|
||||
showWarn: function (message, options) {
|
||||
options = options || {};
|
||||
|
||||
if (!options.doNotClosePassive) {
|
||||
this.closePassive();
|
||||
}
|
||||
|
||||
this.handleNotification({
|
||||
type: 'warn',
|
||||
message: message
|
||||
}, options.delayed);
|
||||
},
|
||||
|
||||
displayDelayed: function () {
|
||||
var self = this;
|
||||
|
||||
|
@ -145,7 +101,7 @@ export default Ember.Service.extend({
|
|||
closeNotification: function (notification) {
|
||||
var content = this.get('content');
|
||||
|
||||
if (notification instanceof Notification) {
|
||||
if (typeof notification.toJSON === 'function') {
|
||||
notification.deleteRecord();
|
||||
notification.save().finally(function () {
|
||||
content.removeObject(notification);
|
||||
|
@ -155,12 +111,8 @@ export default Ember.Service.extend({
|
|||
}
|
||||
},
|
||||
|
||||
closePassive: function () {
|
||||
this.set('content', this.get('content').rejectBy('status', 'passive'));
|
||||
},
|
||||
|
||||
closePersistent: function () {
|
||||
this.set('content', this.get('content').rejectBy('status', 'persistent'));
|
||||
closeNotifications: function () {
|
||||
this.set('content', this.get('content').rejectBy('status', 'notification'));
|
||||
},
|
||||
|
||||
closeAll: function () {
|
||||
|
|
|
@ -63,6 +63,16 @@
|
|||
color: var(--red);
|
||||
}
|
||||
|
||||
.gh-notification-passive {
|
||||
animation: fade-out;
|
||||
animation-delay: 5s;
|
||||
animation-iteration-count: 1;
|
||||
}
|
||||
|
||||
.gh-notification-passive:hover {
|
||||
animation: fade-in;
|
||||
}
|
||||
|
||||
/* Red notification
|
||||
/* ---------------------------------------------------------- */
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
"password-generator": "git://github.com/bermi/password-generator#49accd7",
|
||||
"rangyinputs": "1.2.0",
|
||||
"showdown-ghost": "0.3.6",
|
||||
"sinonjs": "1.14.1",
|
||||
"validator-js": "3.39.0",
|
||||
"xregexp": "2.0.0"
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
"ember-data": "1.0.0-beta.18",
|
||||
"ember-export-application-global": "^1.0.2",
|
||||
"ember-myth": "0.1.0",
|
||||
"ember-sinon": "0.2.1",
|
||||
"fs-extra": "0.16.3",
|
||||
"glob": "^4.0.5"
|
||||
},
|
||||
|
|
71
core/client/tests/unit/components/gh-alert-test.js
Normal file
71
core/client/tests/unit/components/gh-alert-test.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
/* jshint expr:true */
|
||||
import { expect } from 'chai';
|
||||
import {
|
||||
describeComponent,
|
||||
it
|
||||
}
|
||||
from 'ember-mocha';
|
||||
import sinon from 'sinon';
|
||||
|
||||
describeComponent(
|
||||
'gh-alert',
|
||||
'GhAlertComponent', {
|
||||
// specify the other units that are required for this test
|
||||
// needs: ['component:foo', 'helper:bar']
|
||||
},
|
||||
function () {
|
||||
it('renders', function () {
|
||||
// creates the component instance
|
||||
var component = this.subject();
|
||||
expect(component._state).to.equal('preRender');
|
||||
|
||||
component.set('message', {message: 'Test message', type: 'success'});
|
||||
|
||||
// renders the component on the page
|
||||
this.render();
|
||||
expect(component._state).to.equal('inDOM');
|
||||
|
||||
expect(this.$().prop('tagName')).to.equal('ARTICLE');
|
||||
expect(this.$().hasClass('gh-alert')).to.be.true;
|
||||
expect(this.$().text()).to.match(/Test message/);
|
||||
});
|
||||
|
||||
it('maps success alert type to correct class', function () {
|
||||
var component = this.subject();
|
||||
component.set('message', {message: 'Test message', type: 'success'});
|
||||
expect(this.$().hasClass('gh-alert-green')).to.be.true;
|
||||
});
|
||||
|
||||
it('maps error alert type to correct class', function () {
|
||||
var component = this.subject();
|
||||
component.set('message', {message: 'Test message', type: 'error'});
|
||||
expect(this.$().hasClass('gh-alert-red')).to.be.true;
|
||||
});
|
||||
|
||||
it('maps warn alert type to correct class', function () {
|
||||
var component = this.subject();
|
||||
component.set('message', {message: 'Test message', type: 'warn'});
|
||||
expect(this.$().hasClass('gh-alert-yellow')).to.be.true;
|
||||
});
|
||||
|
||||
it('maps info alert type to correct class', function () {
|
||||
var component = this.subject();
|
||||
component.set('message', {message: 'Test message', type: 'info'});
|
||||
expect(this.$().hasClass('gh-alert-blue')).to.be.true;
|
||||
});
|
||||
|
||||
it('closes notification through notifications service', function () {
|
||||
var component = this.subject(),
|
||||
notifications = {},
|
||||
notification = {message: 'Test close', type: 'success'};
|
||||
|
||||
notifications.closeNotification = sinon.spy();
|
||||
component.set('notifications', notifications);
|
||||
component.set('message', notification);
|
||||
|
||||
this.$().find('button').click();
|
||||
|
||||
expect(notifications.closeNotification.calledWith(notification)).to.be.true;
|
||||
});
|
||||
}
|
||||
);
|
63
core/client/tests/unit/components/gh-alerts-test.js
Normal file
63
core/client/tests/unit/components/gh-alerts-test.js
Normal file
|
@ -0,0 +1,63 @@
|
|||
/* jshint expr:true */
|
||||
import Ember from 'ember';
|
||||
import { expect } from 'chai';
|
||||
import {
|
||||
describeComponent,
|
||||
it
|
||||
}
|
||||
from 'ember-mocha';
|
||||
import sinon from 'sinon';
|
||||
|
||||
describeComponent(
|
||||
'gh-alerts',
|
||||
'GhAlertsComponent', {
|
||||
// specify the other units that are required for this test
|
||||
needs: ['component:gh-alert']
|
||||
},
|
||||
function () {
|
||||
beforeEach(function () {
|
||||
// Stub the notifications service
|
||||
var notifications = Ember.Object.create();
|
||||
notifications.alerts = Ember.A();
|
||||
notifications.alerts.pushObject({message: 'First', type: 'error'});
|
||||
notifications.alerts.pushObject({message: 'Second', type: 'warn'});
|
||||
|
||||
this.subject().set('notifications', notifications);
|
||||
});
|
||||
|
||||
it('renders', function () {
|
||||
// creates the component instance
|
||||
var component = this.subject();
|
||||
expect(component._state).to.equal('preRender');
|
||||
|
||||
// renders the component on the page
|
||||
this.render();
|
||||
expect(component._state).to.equal('inDOM');
|
||||
|
||||
expect(this.$().prop('tagName')).to.equal('ASIDE');
|
||||
expect(this.$().hasClass('gh-alerts')).to.be.true;
|
||||
expect(this.$().children().length).to.equal(2);
|
||||
|
||||
Ember.run(function () {
|
||||
component.set('notifications.alerts', Ember.A());
|
||||
});
|
||||
|
||||
expect(this.$().children().length).to.equal(0);
|
||||
});
|
||||
|
||||
it('triggers "notify" action when message count changes', function () {
|
||||
var component = this.subject();
|
||||
|
||||
component.sendAction = sinon.spy();
|
||||
|
||||
component.get('notifications.alerts')
|
||||
.pushObject({message: 'New alert', type: 'info'});
|
||||
|
||||
expect(component.sendAction.calledWith('notify', 3)).to.be.true;
|
||||
|
||||
component.set('notifications.alerts', Ember.A());
|
||||
|
||||
expect(component.sendAction.calledWith('notify', 0)).to.be.true;
|
||||
});
|
||||
}
|
||||
);
|
82
core/client/tests/unit/components/gh-notification-test.js
Normal file
82
core/client/tests/unit/components/gh-notification-test.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
/* jshint expr:true */
|
||||
import { expect } from 'chai';
|
||||
import {
|
||||
describeComponent,
|
||||
it
|
||||
}
|
||||
from 'ember-mocha';
|
||||
import sinon from 'sinon';
|
||||
|
||||
describeComponent(
|
||||
'gh-notification',
|
||||
'GhNotificationComponent', {
|
||||
// specify the other units that are required for this test
|
||||
// needs: ['component:foo', 'helper:bar']
|
||||
},
|
||||
function () {
|
||||
it('renders', function () {
|
||||
// creates the component instance
|
||||
var component = this.subject();
|
||||
expect(component._state).to.equal('preRender');
|
||||
|
||||
component.set('message', {message: 'Test message', type: 'success'});
|
||||
|
||||
// renders the component on the page
|
||||
this.render();
|
||||
expect(component._state).to.equal('inDOM');
|
||||
|
||||
expect(this.$().prop('tagName')).to.equal('ARTICLE');
|
||||
expect(this.$().is('.gh-notification, .gh-notification-passive')).to.be.true;
|
||||
expect(this.$().text()).to.match(/Test message/);
|
||||
});
|
||||
|
||||
it('maps success alert type to correct class', function () {
|
||||
var component = this.subject();
|
||||
component.set('message', {message: 'Test message', type: 'success'});
|
||||
expect(this.$().hasClass('gh-notification-green')).to.be.true;
|
||||
});
|
||||
|
||||
it('maps error alert type to correct class', function () {
|
||||
var component = this.subject();
|
||||
component.set('message', {message: 'Test message', type: 'error'});
|
||||
expect(this.$().hasClass('gh-notification-red')).to.be.true;
|
||||
});
|
||||
|
||||
it('maps warn alert type to correct class', function () {
|
||||
var component = this.subject();
|
||||
component.set('message', {message: 'Test message', type: 'warn'});
|
||||
expect(this.$().hasClass('gh-notification-yellow')).to.be.true;
|
||||
});
|
||||
|
||||
it('closes notification through notifications service', function () {
|
||||
var component = this.subject(),
|
||||
notifications = {},
|
||||
notification = {message: 'Test close', type: 'success'};
|
||||
|
||||
notifications.closeNotification = sinon.spy();
|
||||
component.set('notifications', notifications);
|
||||
component.set('message', notification);
|
||||
|
||||
this.$().find('button').click();
|
||||
|
||||
expect(notifications.closeNotification.calledWith(notification)).to.be.true;
|
||||
});
|
||||
|
||||
it('closes notification when animationend event is triggered', function (done) {
|
||||
var component = this.subject(),
|
||||
notifications = {},
|
||||
notification = {message: 'Test close', type: 'success'};
|
||||
|
||||
notifications.closeNotification = sinon.spy();
|
||||
component.set('notifications', notifications);
|
||||
component.set('message', notification);
|
||||
|
||||
// shorten the animation delay to speed up test
|
||||
this.$().css('animation-delay', '0.1s');
|
||||
setTimeout(function () {
|
||||
expect(notifications.closeNotification.calledWith(notification)).to.be.true;
|
||||
done();
|
||||
}, 150);
|
||||
});
|
||||
}
|
||||
);
|
47
core/client/tests/unit/components/gh-notifications-test.js
Normal file
47
core/client/tests/unit/components/gh-notifications-test.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
/* jshint expr:true */
|
||||
import Ember from 'ember';
|
||||
import { expect } from 'chai';
|
||||
import {
|
||||
describeComponent,
|
||||
it
|
||||
}
|
||||
from 'ember-mocha';
|
||||
|
||||
describeComponent(
|
||||
'gh-notifications',
|
||||
'GhNotificationsComponent', {
|
||||
// specify the other units that are required for this test
|
||||
needs: ['component:gh-notification']
|
||||
},
|
||||
function () {
|
||||
beforeEach(function () {
|
||||
// Stub the notifications service
|
||||
var notifications = Ember.Object.create();
|
||||
notifications.notifications = Ember.A();
|
||||
notifications.notifications.pushObject({message: 'First', type: 'error'});
|
||||
notifications.notifications.pushObject({message: 'Second', type: 'warn'});
|
||||
|
||||
this.subject().set('notifications', notifications);
|
||||
});
|
||||
|
||||
it('renders', function () {
|
||||
// creates the component instance
|
||||
var component = this.subject();
|
||||
expect(component._state).to.equal('preRender');
|
||||
|
||||
// renders the component on the page
|
||||
this.render();
|
||||
expect(component._state).to.equal('inDOM');
|
||||
|
||||
expect(this.$().prop('tagName')).to.equal('ASIDE');
|
||||
expect(this.$().hasClass('gh-notifications')).to.be.true;
|
||||
expect(this.$().children().length).to.equal(2);
|
||||
|
||||
Ember.run(function () {
|
||||
component.set('notifications.notifications', Ember.A());
|
||||
});
|
||||
|
||||
expect(this.$().children().length).to.equal(0);
|
||||
});
|
||||
}
|
||||
);
|
286
core/client/tests/unit/services/notifications-test.js
Normal file
286
core/client/tests/unit/services/notifications-test.js
Normal file
|
@ -0,0 +1,286 @@
|
|||
/* jshint expr:true */
|
||||
import Ember from 'ember';
|
||||
import sinon from 'sinon';
|
||||
import { expect } from 'chai';
|
||||
import {
|
||||
describeModule,
|
||||
it
|
||||
} from 'ember-mocha';
|
||||
|
||||
describeModule(
|
||||
'service:notifications',
|
||||
'NotificationsService',
|
||||
{
|
||||
// Specify the other units that are required for this test.
|
||||
// needs: ['model:notification']
|
||||
},
|
||||
function () {
|
||||
beforeEach(function () {
|
||||
this.subject().set('content', Ember.A());
|
||||
this.subject().set('delayedNotifications', Ember.A());
|
||||
});
|
||||
|
||||
it('filters alerts/notifications', function () {
|
||||
var notifications = this.subject();
|
||||
|
||||
notifications.set('content', [
|
||||
{message: 'Alert', status: 'alert'},
|
||||
{message: 'Notification', status: 'notification'}
|
||||
]);
|
||||
|
||||
expect(notifications.get('alerts'))
|
||||
.to.deep.equal([{message: 'Alert', status: 'alert'}]);
|
||||
|
||||
expect(notifications.get('notifications'))
|
||||
.to.deep.equal([{message: 'Notification', status: 'notification'}]);
|
||||
});
|
||||
|
||||
it('#handleNotification deals with DS.Notification notifications', function () {
|
||||
var notifications = this.subject(),
|
||||
notification = Ember.Object.create({message: '<h1>Test</h1>', status: 'alert'});
|
||||
|
||||
notification.toJSON = function () {};
|
||||
|
||||
notifications.handleNotification(notification);
|
||||
|
||||
notification = notifications.get('alerts')[0];
|
||||
|
||||
// alerts received from the server should be marked html safe
|
||||
expect(notification.get('message')).to.have.property('toHTML');
|
||||
});
|
||||
|
||||
it('#handleNotification defaults to notification if no status supplied', function () {
|
||||
var notifications = this.subject();
|
||||
|
||||
notifications.handleNotification({message: 'Test'}, false);
|
||||
|
||||
expect(notifications.get('content'))
|
||||
.to.deep.include({message: 'Test', status: 'notification'});
|
||||
});
|
||||
|
||||
it('#showAlert adds POJO alerts', function () {
|
||||
var notifications = this.subject();
|
||||
|
||||
notifications.showAlert('Test Alert', {type: 'error'});
|
||||
|
||||
expect(notifications.get('alerts'))
|
||||
.to.deep.include({message: 'Test Alert', status: 'alert', type: 'error'});
|
||||
});
|
||||
|
||||
it('#showAlert adds delayed notifications', function () {
|
||||
var notifications = this.subject();
|
||||
|
||||
notifications.showNotification('Test Alert', {type: 'error', delayed: true});
|
||||
|
||||
expect(notifications.get('delayedNotifications'))
|
||||
.to.deep.include({message: 'Test Alert', status: 'notification', type: 'error'});
|
||||
});
|
||||
|
||||
it('#showNotification adds POJO notifications', function () {
|
||||
var notifications = this.subject();
|
||||
|
||||
notifications.showNotification('Test Notification', {type: 'success'});
|
||||
|
||||
expect(notifications.get('notifications'))
|
||||
.to.deep.include({message: 'Test Notification', status: 'notification', type: 'success'});
|
||||
});
|
||||
|
||||
it('#showNotification adds delayed notifications', function () {
|
||||
var notifications = this.subject();
|
||||
|
||||
notifications.showNotification('Test Notification', {delayed: true});
|
||||
|
||||
expect(notifications.get('delayedNotifications'))
|
||||
.to.deep.include({message: 'Test Notification', status: 'notification', type: undefined});
|
||||
});
|
||||
|
||||
it('#showNotification clears existing notifications', function () {
|
||||
var notifications = this.subject();
|
||||
|
||||
notifications.showNotification('First');
|
||||
notifications.showNotification('Second');
|
||||
|
||||
expect(notifications.get('content.length')).to.equal(1);
|
||||
expect(notifications.get('content'))
|
||||
.to.deep.equal([{message: 'Second', status: 'notification', type: undefined}]);
|
||||
});
|
||||
|
||||
it('#showNotification keeps existing notifications if doNotCloseNotifications option passed', function () {
|
||||
var notifications = this.subject();
|
||||
|
||||
notifications.showNotification('First');
|
||||
notifications.showNotification('Second', {doNotCloseNotifications: true});
|
||||
|
||||
expect(notifications.get('content.length')).to.equal(2);
|
||||
});
|
||||
|
||||
// TODO: review whether this can be removed once it's no longer used by validations
|
||||
it('#showErrors adds multiple notifications', function () {
|
||||
var notifications = this.subject();
|
||||
|
||||
notifications.showErrors([
|
||||
{message: 'First'},
|
||||
{message: 'Second'}
|
||||
]);
|
||||
|
||||
expect(notifications.get('content')).to.deep.equal([
|
||||
{message: 'First', status: 'notification', type: 'error'},
|
||||
{message: 'Second', status: 'notification', type: 'error'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('#showAPIError adds single json response error', function () {
|
||||
var notifications = this.subject(),
|
||||
resp = {jqXHR: {responseJSON: {error: 'Single error'}}};
|
||||
|
||||
notifications.showAPIError(resp);
|
||||
|
||||
expect(notifications.get('content')).to.deep.equal([
|
||||
{message: 'Single error', status: 'alert', type: 'error'}
|
||||
]);
|
||||
});
|
||||
|
||||
// used to display validation errors returned from the server
|
||||
it('#showAPIError adds multiple json response errors', function () {
|
||||
var notifications = this.subject(),
|
||||
resp = {jqXHR: {responseJSON: {errors: ['First error', 'Second error']}}};
|
||||
|
||||
notifications.showAPIError(resp);
|
||||
|
||||
expect(notifications.get('content')).to.deep.equal([
|
||||
{message: 'First error', status: 'notification', type: 'error'},
|
||||
{message: 'Second error', status: 'notification', type: 'error'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('#showAPIError adds single json response message', function () {
|
||||
var notifications = this.subject(),
|
||||
resp = {jqXHR: {responseJSON: {message: 'Single message'}}};
|
||||
|
||||
notifications.showAPIError(resp);
|
||||
|
||||
expect(notifications.get('content')).to.deep.equal([
|
||||
{message: 'Single message', status: 'alert', type: 'error'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('#showAPIError displays default error text if response has no error/message', function () {
|
||||
var notifications = this.subject(),
|
||||
resp = {};
|
||||
|
||||
notifications.showAPIError(resp);
|
||||
expect(notifications.get('content')).to.deep.equal([
|
||||
{message: 'There was a problem on the server, please try again.', status: 'alert', type: 'error'}
|
||||
]);
|
||||
|
||||
notifications.set('content', Ember.A());
|
||||
|
||||
notifications.showAPIError(resp, {defaultErrorText: 'Overridden default'});
|
||||
expect(notifications.get('content')).to.deep.equal([
|
||||
{message: 'Overridden default', status: 'alert', type: 'error'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('#displayDelayed moves delayed notifications into content', function () {
|
||||
var notifications = this.subject();
|
||||
|
||||
notifications.showNotification('First', {delayed: true});
|
||||
notifications.showNotification('Second', {delayed: true});
|
||||
notifications.showNotification('Third', {delayed: false});
|
||||
|
||||
notifications.displayDelayed();
|
||||
|
||||
expect(notifications.get('content')).to.deep.equal([
|
||||
{message: 'Third', status: 'notification', type: undefined},
|
||||
{message: 'First', status: 'notification', type: undefined},
|
||||
{message: 'Second', status: 'notification', type: undefined}
|
||||
]);
|
||||
});
|
||||
|
||||
it('#closeNotification removes POJO notifications', function () {
|
||||
var notification = {message: 'Close test', status: 'notification'},
|
||||
notifications = this.subject();
|
||||
|
||||
notifications.handleNotification(notification);
|
||||
|
||||
expect(notifications.get('notifications'))
|
||||
.to.include(notification);
|
||||
|
||||
notifications.closeNotification(notification);
|
||||
|
||||
expect(notifications.get('notifications'))
|
||||
.to.not.include(notification);
|
||||
});
|
||||
|
||||
it('#closeNotification removes and deletes DS.Notification records', function () {
|
||||
var notification = Ember.Object.create({message: 'Close test', status: 'alert'}),
|
||||
notifications = this.subject();
|
||||
|
||||
notification.toJSON = function () {};
|
||||
notification.deleteRecord = function () {};
|
||||
sinon.spy(notification, 'deleteRecord');
|
||||
notification.save = function () {
|
||||
return {
|
||||
finally: function (callback) { return callback(notification); }
|
||||
};
|
||||
};
|
||||
sinon.spy(notification, 'save');
|
||||
|
||||
notifications.handleNotification(notification);
|
||||
expect(notifications.get('alerts')).to.include(notification);
|
||||
|
||||
notifications.closeNotification(notification);
|
||||
|
||||
expect(notification.deleteRecord.calledOnce).to.be.true;
|
||||
expect(notification.save.calledOnce).to.be.true;
|
||||
|
||||
// wrap in runloop so filter updates
|
||||
Ember.run.next(function () {
|
||||
expect(notifications.get('alerts')).to.not.include(notification);
|
||||
});
|
||||
});
|
||||
|
||||
it('#closeNotifications only removes notifications', function () {
|
||||
var notifications = this.subject();
|
||||
|
||||
notifications.showAlert('First alert');
|
||||
notifications.showNotification('First notification');
|
||||
notifications.showNotification('Second notification', {doNotCloseNotifications: true});
|
||||
|
||||
expect(notifications.get('alerts.length')).to.equal(1);
|
||||
expect(notifications.get('notifications.length')).to.equal(2);
|
||||
|
||||
notifications.closeNotifications();
|
||||
|
||||
// wrap in runloop so filter updates
|
||||
Ember.run.next(function () {
|
||||
expect(notifications.get('alerts.length')).to.equal(1);
|
||||
expect(notifications.get('notifications.length')).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('#closeAll removes everything without deletion', function () {
|
||||
var notifications = this.subject(),
|
||||
notificationModel = Ember.Object.create({message: 'model'});
|
||||
|
||||
notificationModel.toJSON = function () {};
|
||||
notificationModel.deleteRecord = function () {};
|
||||
sinon.spy(notificationModel, 'deleteRecord');
|
||||
notificationModel.save = function () {
|
||||
return {
|
||||
finally: function (callback) { return callback(notificationModel); }
|
||||
};
|
||||
};
|
||||
sinon.spy(notificationModel, 'save');
|
||||
|
||||
notifications.handleNotification(notificationModel);
|
||||
notifications.handleNotification({message: 'pojo'});
|
||||
|
||||
notifications.closeAll();
|
||||
|
||||
expect(notifications.get('content')).to.be.empty;
|
||||
expect(notificationModel.deleteRecord.called).to.be.false;
|
||||
expect(notificationModel.save.called).to.be.false;
|
||||
});
|
||||
}
|
||||
);
|
|
@ -50,7 +50,7 @@ notifications = {
|
|||
var defaults = {
|
||||
dismissible: true,
|
||||
location: 'bottom',
|
||||
status: 'persistent'
|
||||
status: 'alert'
|
||||
},
|
||||
addedNotifications = [];
|
||||
|
||||
|
@ -61,7 +61,7 @@ notifications = {
|
|||
|
||||
notification = _.assign(defaults, notification, {
|
||||
id: notificationCounter
|
||||
// status: 'persistent'
|
||||
// status: 'alert'
|
||||
});
|
||||
|
||||
notificationsStore.push(notification);
|
||||
|
|
|
@ -36,7 +36,7 @@ adminControllers = {
|
|||
type: 'upgrade',
|
||||
location: 'settings-about-upgrade',
|
||||
dismissible: false,
|
||||
status: 'persistent',
|
||||
status: 'alert',
|
||||
message: 'Ghost ' + updateVersion + ' is available! Hot Damn. <a href="http://support.ghost.org/how-to-upgrade/" target="_blank">Click here</a> to upgrade.'
|
||||
};
|
||||
|
||||
|
|
|
@ -411,9 +411,9 @@ CasperTest.Routines = (function () {
|
|||
|
||||
casper.captureScreenshot('setting_up2.png');
|
||||
|
||||
casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
|
||||
casper.waitForSelectorTextChange('.gh-alert-success', function onSuccess() {
|
||||
var errorText = casper.evaluate(function () {
|
||||
return document.querySelector('.notification-error').innerText;
|
||||
return document.querySelector('.gh-alert').innerText;
|
||||
});
|
||||
casper.echoConcise('Setup failed. Error text: ' + errorText);
|
||||
}, function onTimeout() {
|
||||
|
|
|
@ -26,7 +26,7 @@ CasperTest.begin('Ghost editor functions correctly', 16, function suite(test) {
|
|||
|
||||
casper.thenClick('.js-publish-button');
|
||||
|
||||
casper.waitForSelector('.notification-success', function onSuccess() {
|
||||
casper.waitForSelector('.gh-notification', function onSuccess() {
|
||||
test.assert(true, 'Can save with no title.');
|
||||
test.assertEvalEquals(function () {
|
||||
return document.getElementById('entry-title').value;
|
||||
|
@ -58,7 +58,7 @@ CasperTest.begin('Ghost editor functions correctly', 16, function suite(test) {
|
|||
|
||||
casper.thenClick('.js-publish-button');
|
||||
|
||||
casper.waitForSelector('.notification-success', function onSuccess() {
|
||||
casper.waitForSelector('.gh-notification', function onSuccess() {
|
||||
test.assertUrlMatch(/ghost\/editor\/\d+\/$/, 'got an id on our URL');
|
||||
test.assertEvalEquals(function () {
|
||||
return document.querySelector('#entry-title').value;
|
||||
|
@ -223,7 +223,7 @@ CasperTest.begin('Image Uploads', 23, function suite(test) {
|
|||
// Save the post with the image
|
||||
casper.thenClick('.js-publish-button');
|
||||
|
||||
casper.waitForSelector('.notification-success', function onSuccess() {
|
||||
casper.waitForSelector('.gh-notification', function onSuccess() {
|
||||
test.assertUrlMatch(/ghost\/editor\/\d+\/$/, 'got an id on our URL');
|
||||
}, casper.failOnTimeout(test, 'Post was not successfully created'));
|
||||
|
||||
|
@ -385,7 +385,7 @@ CasperTest.begin('Publish menu - existing post', 23, function suite(test) {
|
|||
// Create a post in draft status
|
||||
casper.thenClick('.js-publish-button');
|
||||
|
||||
casper.waitForSelector('.notification-success', function checkPostWasCreated() {
|
||||
casper.waitForSelector('.gh-notification', function checkPostWasCreated() {
|
||||
test.assertUrlMatch(/ghost\/editor\/\d+\/$/, 'got an id on our URL');
|
||||
});
|
||||
|
||||
|
@ -429,7 +429,7 @@ CasperTest.begin('Publish menu - existing post', 23, function suite(test) {
|
|||
// Do publish
|
||||
casper.thenClick('.js-publish-button');
|
||||
|
||||
casper.waitForSelector('.notification-success', function checkPostWasCreated() {
|
||||
casper.waitForSelector('.gh-notification', function checkPostWasCreated() {
|
||||
test.assertUrlMatch(/ghost\/editor\/\d+\/$/, 'got an id on our URL');
|
||||
});
|
||||
|
||||
|
@ -461,7 +461,7 @@ CasperTest.begin('Publish menu - existing post', 23, function suite(test) {
|
|||
// Do unpublish
|
||||
casper.thenClick('.js-publish-button');
|
||||
|
||||
casper.waitForSelector('.notification-success', function checkPostWasCreated() {
|
||||
casper.waitForSelector('.gh-notification', function checkPostWasCreated() {
|
||||
// ... check status, label, class
|
||||
casper.waitForSelector('.js-publish-splitbutton', function onSuccess() {
|
||||
test.assertExists('.js-publish-button.btn-blue', 'Publish button should have .btn-blue');
|
||||
|
@ -472,7 +472,7 @@ CasperTest.begin('Publish menu - existing post', 23, function suite(test) {
|
|||
});
|
||||
});
|
||||
|
||||
CasperTest.begin('Publish menu - delete post', 7, function testDeleteModal(test) {
|
||||
CasperTest.begin('Publish menu - delete post', 6, function testDeleteModal(test) {
|
||||
// Create a post that can be deleted
|
||||
CasperTest.Routines.createTestPost.run(false);
|
||||
|
||||
|
@ -515,15 +515,8 @@ CasperTest.begin('Publish menu - delete post', 7, function testDeleteModal(test)
|
|||
// Delete the post
|
||||
this.click('.modal-content .js-button-accept');
|
||||
|
||||
casper.waitForSelector('.notification-success', function onSuccess() {
|
||||
test.assert(true, 'Got success notification from delete post');
|
||||
test.assertSelectorHasText(
|
||||
'.gh-notification-content',
|
||||
'Your post has been deleted.',
|
||||
'.gh-notification-content has correct text'
|
||||
);
|
||||
}, function onTimeout() {
|
||||
test.fail('No success notification from delete post');
|
||||
casper.waitWhileVisible('.modal-container', function onSuccess() {
|
||||
test.assert(true, 'clicking delete button should close the delete post modal');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -579,7 +572,7 @@ CasperTest.begin('Publish menu - existing post status is correct after failed sa
|
|||
// save
|
||||
casper.thenClick('.js-publish-button');
|
||||
|
||||
casper.waitForSelector('.notification-success');
|
||||
casper.waitForSelector('.gh-notification');
|
||||
|
||||
casper.then(function updateTitle() {
|
||||
casper.sendKeys('#entry-title', new Array(160).join('y'));
|
||||
|
@ -607,15 +600,14 @@ CasperTest.begin('Publish menu - existing post status is correct after failed sa
|
|||
casper.thenClick('.js-publish-button');
|
||||
|
||||
// ... check status, label, class
|
||||
// TODO: re-implement these once #5933 is merged
|
||||
// casper.waitForSelector('.notification-error', function onSuccess() {
|
||||
// test.assertExists('.js-publish-button.btn-blue', 'Update button should have .btn-blue');
|
||||
// // wait for button to settle
|
||||
// casper.wait(500);
|
||||
// test.assertSelectorHasText('.js-publish-button', 'Save Draft', '.js-publish-button says Save Draft');
|
||||
// }, function onTimeout() {
|
||||
// test.assert(false, 'Saving post with invalid title should trigger an error');
|
||||
// });
|
||||
casper.waitForSelector('.gh-alert-red', function onSuccess() {
|
||||
test.assertExists('.js-publish-button.btn-blue', 'Update button should have .btn-blue');
|
||||
// wait for button to settle
|
||||
casper.wait(500);
|
||||
test.assertSelectorHasText('.js-publish-button', 'Save Draft', '.js-publish-button says Save Draft');
|
||||
}, function onTimeout() {
|
||||
test.assert(false, 'Saving post with invalid title should trigger an error');
|
||||
});
|
||||
});
|
||||
|
||||
// test the markdown help modal
|
||||
|
@ -660,7 +652,7 @@ CasperTest.begin('Title input is set correctly after using the Post-Settings-Men
|
|||
// save draft
|
||||
casper.thenClick('.js-publish-button');
|
||||
|
||||
casper.waitForSelector('.notification-success');
|
||||
casper.waitForSelector('.gh-notification');
|
||||
|
||||
// change the title
|
||||
casper.then(function updateTitle() {
|
||||
|
@ -707,7 +699,7 @@ CasperTest.begin('Editor content is set correctly after using the Post-Settings-
|
|||
// save draft
|
||||
casper.thenClick('.js-publish-button');
|
||||
|
||||
casper.waitForSelector('.notification-success');
|
||||
casper.waitForSelector('.gh-notification');
|
||||
|
||||
// change the content
|
||||
casper.then(function updateContent() {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
/*globals CasperTest, casper, __utils__ */
|
||||
|
||||
CasperTest.begin('Post settings menu', 10, function suite(test) {
|
||||
CasperTest.begin('Post settings menu', 8, function suite(test) {
|
||||
casper.thenOpenAndWaitForPageLoad('editor', function testTitleAndUrl() {
|
||||
test.assertTitle('Editor - Test Blog', 'Ghost admin has incorrect title');
|
||||
test.assertUrlMatch(/ghost\/editor\/$/, 'Landed on the correct URL');
|
||||
|
@ -24,16 +24,6 @@ CasperTest.begin('Post settings menu', 10, function suite(test) {
|
|||
casper.thenClick('.js-publish-button');
|
||||
});
|
||||
|
||||
casper.waitForSelector('.notification-success', function waitForSuccess() {
|
||||
test.assert(true, 'got success notification');
|
||||
test.assertSelectorHasText('.notification-success', 'Saved.', '.notification-success has correct text');
|
||||
casper.click('.gh-notification-close');
|
||||
}, function onTimeout() {
|
||||
test.assert(false, 'No success notification');
|
||||
});
|
||||
|
||||
casper.waitWhileSelector('.notification-success');
|
||||
|
||||
casper.thenClick('.post-settings');
|
||||
|
||||
casper.waitForOpaque('.settings-menu', function onSuccess() {
|
||||
|
|
|
@ -21,7 +21,7 @@ CasperTest.begin('Settings screen is correct', 5, function suite(test) {
|
|||
});
|
||||
|
||||
// ## General settings tests
|
||||
CasperTest.begin('General settings pane is correct', 7, function suite(test) {
|
||||
CasperTest.begin('General settings pane is correct', 4, function suite(test) {
|
||||
casper.thenOpenAndWaitForPageLoad('settings.general', function testTitleAndUrl() {
|
||||
test.assertUrlMatch(/ghost\/settings\/general\/$/, 'Landed on the correct URL');
|
||||
});
|
||||
|
@ -29,9 +29,6 @@ CasperTest.begin('General settings pane is correct', 7, function suite(test) {
|
|||
function assertImageUploaderModalThenClose() {
|
||||
test.assertSelectorHasText('.description', 'Add image', '.description has the correct text');
|
||||
casper.click('.modal-container .js-button-accept');
|
||||
casper.waitForSelector('.notification-success', function onSuccess() {
|
||||
test.assert(true, 'Got success notification');
|
||||
}, casper.failOnTimeout(test, 'No success notification'));
|
||||
}
|
||||
|
||||
// Ensure image upload modals display correctly
|
||||
|
@ -65,22 +62,17 @@ CasperTest.begin('General settings pane is correct', 7, function suite(test) {
|
|||
|
||||
// Ensure can save
|
||||
casper.waitForSelector('header .btn-blue').then(function () {
|
||||
casper.thenClick('header .btn-blue').waitFor(function successNotification() {
|
||||
return this.evaluate(function () {
|
||||
return document.querySelectorAll('.gh-notification').length > 0;
|
||||
});
|
||||
casper.thenClick('header .btn-blue');
|
||||
casper.waitForResource('settings/', function onSuccess() {
|
||||
test.assert(true, 'Settings were saved');
|
||||
}, function doneWaiting() {
|
||||
test.pass('Waited for notification');
|
||||
}, casper.failOnTimeout(test, 'Saving the general pane did not result in a notification'));
|
||||
test.fail('Settings were not saved');
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function checkSettingsWereSaved() {
|
||||
casper.then(function stopListeningForRequests() {
|
||||
casper.removeListener('resource.requested', handleSettingsRequest);
|
||||
});
|
||||
|
||||
casper.waitForSelector('.notification-success', function onSuccess() {
|
||||
test.assert(true, 'Got success notification');
|
||||
}, casper.failOnTimeout(test, 'No success notification :('));
|
||||
});
|
||||
|
||||
// ## General settings validations tests
|
||||
|
@ -96,24 +88,24 @@ CasperTest.begin('General settings validation is correct', 4, function suite(tes
|
|||
'general[title]': new Array(152).join('a')
|
||||
});
|
||||
|
||||
// TODO: re-implement once #5933 is merged
|
||||
// casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
|
||||
// test.assertSelectorHasText('.notification-error', 'too long', '.notification-error has correct text');
|
||||
// }, casper.failOnTimeout(test, 'Blog title length error did not appear'), 2000);
|
||||
// TODO: review once inline-validations are implemented
|
||||
casper.waitForSelectorTextChange('.gh-notification-red', function onSuccess() {
|
||||
test.assertSelectorHasText('.gh-notification-red', 'too long', '.gh-notification-red has correct text');
|
||||
}, casper.failOnTimeout(test, 'Blog title length error did not appear'), 2000);
|
||||
|
||||
casper.thenClick('.gh-notification-close');
|
||||
casper.thenClick('.gh-alert-close');
|
||||
|
||||
// Ensure general blog description field length validation
|
||||
casper.fillAndSave('form#settings-general', {
|
||||
'general[description]': new Array(202).join('a')
|
||||
});
|
||||
|
||||
// TODO: re-implement once #5933 is merged
|
||||
// casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
|
||||
// test.assertSelectorHasText('.notification-error', 'too long', '.notification-error has correct text');
|
||||
// }, casper.failOnTimeout(test, 'Blog description length error did not appear'));
|
||||
// TODO: review once inline-validations are implemented
|
||||
casper.waitForSelectorTextChange('.gh-notification-red', function onSuccess() {
|
||||
test.assertSelectorHasText('.gh-notification-red', 'too long', '.gh-notification-red has correct text');
|
||||
}, casper.failOnTimeout(test, 'Blog description length error did not appear'));
|
||||
|
||||
casper.thenClick('.gh-notification-close');
|
||||
casper.thenClick('.gh-alert-close');
|
||||
|
||||
// TODO move these to ember tests, note: async issues - field will be often be null without a casper.wait
|
||||
// Check postsPerPage autocorrect
|
||||
|
|
|
@ -52,7 +52,7 @@ CasperTest.begin('Login limit is in place', 4, function suite(test) {
|
|||
|
||||
casper.waitForText('remaining', function onSuccess() {
|
||||
test.assert(true, 'The login limit is in place.');
|
||||
test.assertSelectorDoesntHaveText('.notification-error', '[object Object]');
|
||||
test.assertSelectorDoesntHaveText('.gh-alert', '[object Object]');
|
||||
}, function onTimeout() {
|
||||
test.assert(false, 'We did not trip the login limit.');
|
||||
});
|
||||
|
@ -129,21 +129,23 @@ CasperTest.begin('Ensure email field form validation', 2, function suite(test) {
|
|||
test.fail('Login form didn\'t fade in.');
|
||||
});
|
||||
|
||||
// casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
|
||||
// test.assertSelectorHasText('.notification-error', 'Invalid Email', '.notification-error text is correct');
|
||||
// }, function onTimeout() {
|
||||
// test.fail('Email validation error did not appear');
|
||||
// }, 2000);
|
||||
//
|
||||
// casper.then(function testMissingEmail() {
|
||||
// this.fillAndSave('form.gh-signin', {
|
||||
// identification: ''
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
|
||||
// test.assertSelectorHasText('.notification-error', 'Please enter an email', '.notification-error text is correct');
|
||||
// }, function onTimeout() {
|
||||
// test.fail('Missing Email validation error did not appear');
|
||||
// }, 2000);
|
||||
// TODO: review once inline-validations are implemented
|
||||
casper.waitForSelectorTextChange('.gh-notification-red', function onSuccess() {
|
||||
test.assertSelectorHasText('.gh-notification-red', 'Invalid Email', '.gh-notification-red text is correct');
|
||||
}, function onTimeout() {
|
||||
test.fail('Email validation error did not appear');
|
||||
}, 2000);
|
||||
|
||||
casper.then(function testMissingEmail() {
|
||||
this.fillAndSave('form.gh-signin', {
|
||||
identification: ''
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: review once inline-validations are implemented
|
||||
casper.waitForSelectorTextChange('.gh-notification-red', function onSuccess() {
|
||||
test.assertSelectorHasText('.gh-notification-red', 'Please enter an email', '.gh-notification-red text is correct');
|
||||
}, function onTimeout() {
|
||||
test.fail('Missing Email validation error did not appear');
|
||||
}, 2000);
|
||||
}, true);
|
||||
|
|
|
@ -71,7 +71,7 @@ CasperTest.begin('Users screen is correct', 9, function suite(test) {
|
|||
});
|
||||
|
||||
// ### User settings tests
|
||||
CasperTest.begin('Can save settings', 7, function suite(test) {
|
||||
CasperTest.begin('Can save settings', 5, function suite(test) {
|
||||
casper.thenOpenAndWaitForPageLoad('team.user', function testTitleAndUrl() {
|
||||
test.assertTitle('Team - User - Test Blog', 'Ghost Admin title is correct');
|
||||
test.assertUrlMatch(/ghost\/team\/test\/$/, 'team.user has correct URL');
|
||||
|
@ -96,22 +96,16 @@ CasperTest.begin('Can save settings', 7, function suite(test) {
|
|||
});
|
||||
|
||||
casper.thenClick('.btn-blue');
|
||||
casper.waitFor(function successNotification() {
|
||||
return this.evaluate(function () {
|
||||
return document.querySelectorAll('.gh-notification').length > 0;
|
||||
});
|
||||
casper.waitForResource(/\/users\/\d\/\?include=roles/, function onSuccess() {
|
||||
test.assert(true, 'Saving the user pane triggered a save request');
|
||||
}, function doneWaiting() {
|
||||
test.pass('Waited for notification');
|
||||
}, casper.failOnTimeout(test, 'Saving the user pane did not result in a notification'));
|
||||
test.fail('Saving the user pane did not trigger a save request');
|
||||
});
|
||||
|
||||
casper.then(function checkUserWasSaved() {
|
||||
casper.removeListener('resource.requested', handleUserRequest);
|
||||
});
|
||||
|
||||
casper.waitForSelector('.notification-success', function onSuccess() {
|
||||
test.assert(true, 'Got success notification');
|
||||
}, casper.failOnTimeout(test, 'No success notification :('));
|
||||
|
||||
casper.thenClick('.gh-nav-settings-general').then(function testTransitionToGeneral() {
|
||||
casper.waitForSelector(generalTabDetector, function then() {
|
||||
casper.on('resource.requested', handleSettingsRequest);
|
||||
|
@ -122,22 +116,17 @@ CasperTest.begin('Can save settings', 7, function suite(test) {
|
|||
casper.failOnTimeout(test, 'waitForSelector `usersTabDetector` timed out'));
|
||||
});
|
||||
|
||||
casper.thenClick('.btn-blue').waitFor(function successNotification() {
|
||||
return this.evaluate(function () {
|
||||
return document.querySelectorAll('.gh-notification').length > 0;
|
||||
});
|
||||
casper.thenClick('.btn-blue');
|
||||
casper.waitForResource(/\/users\/\d\/\?include=roles/, function onSuccess() {
|
||||
test.assert(true, 'Saving the user pane triggered a save request');
|
||||
}, function doneWaiting() {
|
||||
test.pass('Waited for notification');
|
||||
}, casper.failOnTimeout(test, 'Saving the general pane did not result in a notification'));
|
||||
test.fail('Saving the user pane did not trigger a save request');
|
||||
});
|
||||
|
||||
casper.then(function checkSettingsWereSaved() {
|
||||
casper.removeListener('resource.requested', handleSettingsRequest);
|
||||
});
|
||||
|
||||
casper.waitForSelector('.notification-success', function onSuccess() {
|
||||
test.assert(true, 'Got success notification');
|
||||
}, casper.failOnTimeout(test, 'No success notification :('));
|
||||
|
||||
CasperTest.beforeDone(function () {
|
||||
casper.removeListener('resource.requested', handleUserRequest);
|
||||
casper.removeListener('resource.requested', handleSettingsRequest);
|
||||
|
@ -203,8 +192,7 @@ CasperTest.begin('User settings screen change slug handles duplicate slug', 4, f
|
|||
});
|
||||
});
|
||||
|
||||
// TODO: Change number of tests back to 6 once the commented-out tests are fixed
|
||||
CasperTest.begin('User settings screen validates email', 4, function suite(test) {
|
||||
CasperTest.begin('User settings screen validates email', 6, function suite(test) {
|
||||
var email;
|
||||
|
||||
casper.thenOpenAndWaitForPageLoad('team.user', function testTitleAndUrl() {
|
||||
|
@ -230,11 +218,11 @@ CasperTest.begin('User settings screen validates email', 4, function suite(test)
|
|||
|
||||
casper.waitForResource('/team/');
|
||||
|
||||
// TODO: Re-implement after inlin-errors is merged
|
||||
// casper.waitForSelector('.notification-error', function onSuccess() {
|
||||
// test.assert(true, 'Got error notification');
|
||||
// test.assertSelectorDoesntHaveText('.notification-error', '[object Object]', 'notification text is not broken');
|
||||
// }, casper.failOnTimeout(test, 'No error notification :('));
|
||||
// TODO: review once inline-validations are implemented
|
||||
casper.waitForSelector('.gh-notification', function onSuccess() {
|
||||
test.assert(true, 'Got error notification');
|
||||
test.assertSelectorDoesntHaveText('.gh-notification', '[object Object]', 'notification text is not broken');
|
||||
}, casper.failOnTimeout(test, 'No error notification :('));
|
||||
|
||||
casper.then(function resetEmailToValid() {
|
||||
casper.fillSelectors('.user-profile', {
|
||||
|
@ -245,11 +233,6 @@ CasperTest.begin('User settings screen validates email', 4, function suite(test)
|
|||
casper.thenClick('.view-actions .btn-blue');
|
||||
|
||||
casper.waitForResource(/users/);
|
||||
|
||||
casper.waitForSelector('.notification-success', function onSuccess() {
|
||||
test.assert(true, 'Got success notification');
|
||||
test.assertSelectorDoesntHaveText('.notification-success', '[object Object]', 'notification text is not broken');
|
||||
}, casper.failOnTimeout(test, 'No success notification :('));
|
||||
});
|
||||
|
||||
// TODO: user needs to be loaded whenever it is edited (multi user)
|
||||
|
@ -278,8 +261,7 @@ CasperTest.begin('User settings screen shows remaining characters for Bio proper
|
|||
});
|
||||
});
|
||||
|
||||
// TODO: Change number of tests back to 3 once the commented-out tests are fixed
|
||||
CasperTest.begin('Ensure user bio field length validation', 2, function suite(test) {
|
||||
CasperTest.begin('Ensure user bio field length validation', 3, function suite(test) {
|
||||
casper.thenOpenAndWaitForPageLoad('team.user', function testTitleAndUrl() {
|
||||
test.assertTitle('Team - User - Test Blog', 'Ghost admin has incorrect title');
|
||||
test.assertUrlMatch(/ghost\/team\/test\/$/, 'Ghost doesn\'t require login this time');
|
||||
|
@ -293,14 +275,13 @@ CasperTest.begin('Ensure user bio field length validation', 2, function suite(te
|
|||
|
||||
casper.thenClick('.view-actions .btn-blue');
|
||||
|
||||
// TODO: re-implement after inline-errors is complete
|
||||
// casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
|
||||
// test.assertSelectorHasText('.notification-error', 'is too long', '.notification-error text is correct');
|
||||
// }, casper.failOnTimeout(test, 'Bio field length error did not appear', 2000));
|
||||
// TODO: review once inline-validations are implemented
|
||||
casper.waitForSelectorTextChange('.gh-notification', function onSuccess() {
|
||||
test.assertSelectorHasText('.gh-notification', 'is too long', '.gh-notification text is correct');
|
||||
}, casper.failOnTimeout(test, 'Bio field length error did not appear', 2000));
|
||||
});
|
||||
|
||||
// TODO: Change number of tests back to 3 once the commented-out tests are fixed
|
||||
CasperTest.begin('Ensure user url field validation', 2, function suite(test) {
|
||||
CasperTest.begin('Ensure user url field validation', 3, function suite(test) {
|
||||
casper.thenOpenAndWaitForPageLoad('team.user', function testTitleAndUrl() {
|
||||
test.assertTitle('Team - User - Test Blog', 'Ghost admin has incorrect title');
|
||||
test.assertUrlMatch(/ghost\/team\/test\/$/, 'Ghost doesn\'t require login this time');
|
||||
|
@ -314,14 +295,13 @@ CasperTest.begin('Ensure user url field validation', 2, function suite(test) {
|
|||
|
||||
casper.thenClick('.view-actions .btn-blue');
|
||||
|
||||
// TODO: re-implement after inline-errors is complete
|
||||
// casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
|
||||
// test.assertSelectorHasText('.notification-error', 'not a valid url', '.notification-error text is correct');
|
||||
// }, casper.failOnTimeout(test, 'Url validation error did not appear', 2000));
|
||||
// TODO: review once inline-validations are implemented
|
||||
casper.waitForSelectorTextChange('.gh-notification', function onSuccess() {
|
||||
test.assertSelectorHasText('.gh-notification', 'not a valid url', '.gh-notification text is correct');
|
||||
}, casper.failOnTimeout(test, 'Url validation error did not appear', 2000));
|
||||
});
|
||||
|
||||
// TODO: Change number of tests back to 3 once the commented-out tests are fixed
|
||||
CasperTest.begin('Ensure user location field length validation', 2, function suite(test) {
|
||||
CasperTest.begin('Ensure user location field length validation', 3, function suite(test) {
|
||||
casper.thenOpenAndWaitForPageLoad('team.user', function testTitleAndUrl() {
|
||||
test.assertTitle('Team - User - Test Blog', 'Ghost admin has incorrect title');
|
||||
test.assertUrlMatch(/ghost\/team\/test\/$/, 'Ghost doesn\'t require login this time');
|
||||
|
@ -335,8 +315,8 @@ CasperTest.begin('Ensure user location field length validation', 2, function sui
|
|||
|
||||
casper.thenClick('.view-actions .btn-blue');
|
||||
|
||||
// TODO: re-implement after inline-errors is complete
|
||||
// casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
|
||||
// test.assertSelectorHasText('.notification-error', 'is too long', '.notification-error text is correct');
|
||||
// }, casper.failOnTimeout(test, 'Location field length error did not appear', 2000));
|
||||
// TODO: review once inline-validations are implemented
|
||||
casper.waitForSelectorTextChange('.gh-notification', function onSuccess() {
|
||||
test.assertSelectorHasText('.gh-notification', 'is too long', '.gh-notification text is correct');
|
||||
}, casper.failOnTimeout(test, 'Location field length error did not appear', 2000));
|
||||
});
|
||||
|
|
|
@ -55,7 +55,7 @@ describe('Notifications API', function () {
|
|||
|
||||
jsonResponse.notifications[0].type.should.equal(newNotification.type);
|
||||
jsonResponse.notifications[0].message.should.equal(newNotification.message);
|
||||
jsonResponse.notifications[0].status.should.equal('persistent');
|
||||
jsonResponse.notifications[0].status.should.equal('alert');
|
||||
|
||||
done();
|
||||
});
|
||||
|
@ -66,7 +66,7 @@ describe('Notifications API', function () {
|
|||
var newNotification = {
|
||||
type: 'info',
|
||||
message: 'test notification',
|
||||
status: 'persistent'
|
||||
status: 'alert'
|
||||
};
|
||||
|
||||
it('deletes a notification', function (done) {
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
/*global CasperTest, casper, email, user, password */
|
||||
|
||||
// TODO: change test number to 12 after inline-errors are fixed
|
||||
CasperTest.begin('Ghost setup fails properly', 10, function suite(test) {
|
||||
CasperTest.begin('Ghost setup fails properly', 12, function suite(test) {
|
||||
casper.thenOpenAndWaitForPageLoad('setup', function then() {
|
||||
test.assertUrlMatch(/ghost\/setup\/one\/$/, 'Landed on the correct URL');
|
||||
});
|
||||
|
@ -14,12 +13,12 @@ CasperTest.begin('Ghost setup fails properly', 10, function suite(test) {
|
|||
|
||||
// TODO: Fix tests to support inline validation
|
||||
// should now throw a short password error
|
||||
// casper.waitForSelector('.notification-error', function onSuccess() {
|
||||
// test.assert(true, 'Got error notification');
|
||||
// test.assertSelectorHasText('.notification-error', 'Password must be at least 8 characters long');
|
||||
// }, function onTimeout() {
|
||||
// test.assert(false, 'No error notification :(');
|
||||
// });
|
||||
casper.waitForSelector('.gh-notification', function onSuccess() {
|
||||
test.assert(true, 'Got error notification');
|
||||
test.assertSelectorHasText('.gh-notification', 'Password must be at least 8 characters long');
|
||||
}, function onTimeout() {
|
||||
test.assert(false, 'No error notification :(');
|
||||
});
|
||||
|
||||
casper.then(function setupWithLongPassword() {
|
||||
casper.fillAndAdd('#setup', {'blog-title': 'ghost', name: 'slimer', email: email, password: password});
|
||||
|
@ -32,9 +31,9 @@ CasperTest.begin('Ghost setup fails properly', 10, function suite(test) {
|
|||
casper.thenClick('.gh-flow-content .btn');
|
||||
});
|
||||
|
||||
casper.waitForSelector('.notification-error', function onSuccess() {
|
||||
casper.waitForSelector('.gh-alert', function onSuccess() {
|
||||
test.assert(true, 'Got error notification');
|
||||
test.assertSelectorHasText('.notification-error', 'No users to invite.');
|
||||
test.assertSelectorHasText('.gh-alert', 'No users to invite.');
|
||||
|
||||
test.assertExists('.gh-flow-content .btn-minor', 'Submit button is not minor');
|
||||
test.assertSelectorHasText('.gh-flow-content .btn', 'Invite some users', 'Submit button has wrong text');
|
||||
|
@ -58,9 +57,9 @@ CasperTest.begin('Ghost setup fails properly', 10, function suite(test) {
|
|||
casper.wait(5000);
|
||||
|
||||
// These invitations will fail, because Casper can't send emails
|
||||
casper.waitForSelector('.notification-error', function onSuccess() {
|
||||
casper.waitForSelector('.gh-alert', function onSuccess() {
|
||||
test.assert(true, 'Got error notification');
|
||||
test.assertSelectorHasText('.notification-error', 'Failed to send 2 invitations: test@example.com, test2@example.com');
|
||||
test.assertSelectorHasText('.gh-alert', 'Failed to send 2 invitations: test@example.com, test2@example.com');
|
||||
}, function onTimeout() {
|
||||
test.assert(false, 'No error notification after invite.');
|
||||
});
|
||||
|
|
|
@ -74,7 +74,7 @@ describe('Notifications API', function () {
|
|||
notification.id.should.be.a.Number;
|
||||
notification.id.should.not.equal(99);
|
||||
should.exist(notification.status);
|
||||
notification.status.should.equal('persistent');
|
||||
notification.status.should.equal('alert');
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
|
|
Loading…
Add table
Reference in a new issue