diff --git a/core/client/app/components/gh-editor-save-button.js b/core/client/app/components/gh-editor-save-button.js
index ea806697ab9e..5ddc2ca8dd38 100644
--- a/core/client/app/components/gh-editor-save-button.js
+++ b/core/client/app/components/gh-editor-save-button.js
@@ -9,6 +9,7 @@ export default Ember.Component.extend({
isPublished: null,
willPublish: null,
postOrPage: null,
+ submitting: false,
// Tracks whether we're going to change the state of the post on save
isDangerous: Ember.computed('isPublished', 'willPublish', function () {
diff --git a/core/client/app/components/gh-spin-button.js b/core/client/app/components/gh-spin-button.js
new file mode 100644
index 000000000000..ac9bb6f8d58c
--- /dev/null
+++ b/core/client/app/components/gh-spin-button.js
@@ -0,0 +1,38 @@
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+ tagName: 'button',
+ buttonText: '',
+ submitting: false,
+ autoWidth: true,
+
+ // Disable Button when isLoading equals true
+ attributeBindings: ['disabled'],
+
+ // Must be set on the controller
+ disabled: Ember.computed.equal('submitting', true),
+
+ click: function () {
+ if (this.get('action')) {
+ this.sendAction('action');
+ return false;
+ }
+ return true;
+ },
+
+ setSize: function () {
+ if (!this.get('submitting') && this.get('autoWidth')) {
+ // this exists so that the spinner doesn't change the size of the button
+ this.$().width(this.$().width()); // sets width of button
+ this.$().height(this.$().height()); // sets height of button
+ }
+ },
+
+ width: Ember.observer('buttonText', 'autoWidth', function () {
+ this.setSize();
+ }),
+
+ didInsertElement: function () {
+ this.setSize();
+ }
+});
diff --git a/core/client/app/controllers/modals/signin.js b/core/client/app/controllers/modals/signin.js
index ada9b10280f7..435199472964 100644
--- a/core/client/app/controllers/modals/signin.js
+++ b/core/client/app/controllers/modals/signin.js
@@ -3,6 +3,7 @@ import ValidationEngine from 'ghost/mixins/validation-engine';
export default Ember.Controller.extend(ValidationEngine, {
validationType: 'signin',
+ submitting: false,
application: Ember.inject.controller(),
notifications: Ember.inject.service(),
@@ -28,6 +29,7 @@ export default Ember.Controller.extend(ValidationEngine, {
// it needs to be caught so it doesn't generate an exception in the console,
// but it's actually "handled" by the sessionAuthenticationFailed action handler.
}).finally(function () {
+ self.toggleProperty('submitting');
appController.set('skipAuthSuccessHandler', undefined);
});
},
@@ -35,6 +37,8 @@ export default Ember.Controller.extend(ValidationEngine, {
validateAndAuthenticate: function () {
var self = this;
+ this.toggleProperty('submitting');
+
// Manually trigger events for input fields, ensuring legacy compatibility with
// browsers and password managers that don't send proper events on autofill
$('#login').find('input').trigger('change');
diff --git a/core/client/app/controllers/settings/code-injection.js b/core/client/app/controllers/settings/code-injection.js
index c68c63ee95ae..a197c4e1fbbd 100644
--- a/core/client/app/controllers/settings/code-injection.js
+++ b/core/client/app/controllers/settings/code-injection.js
@@ -1,17 +1,14 @@
import Ember from 'ember';
+import SettingsSaveMixin from 'ghost/mixins/settings-save';
-export default Ember.Controller.extend({
+export default Ember.Controller.extend(SettingsSaveMixin, {
notifications: Ember.inject.service(),
- actions: {
- save: function () {
- var notifications = this.get('notifications');
+ save: function () {
+ var notifications = this.get('notifications');
- return this.get('model').save().then(function (model) {
- return model;
- }).catch(function (error) {
- notifications.showAPIError(error);
- });
- }
+ return this.get('model').save().catch(function (error) {
+ notifications.showAPIError(error);
+ });
}
});
diff --git a/core/client/app/controllers/settings/general.js b/core/client/app/controllers/settings/general.js
index 68a67779a936..cc57f5c21f56 100644
--- a/core/client/app/controllers/settings/general.js
+++ b/core/client/app/controllers/settings/general.js
@@ -1,7 +1,8 @@
import Ember from 'ember';
+import SettingsSaveMixin from 'ghost/mixins/settings-save';
import randomPassword from 'ghost/utils/random-password';
-export default Ember.Controller.extend({
+export default Ember.Controller.extend(SettingsSaveMixin, {
notifications: Ember.inject.service(),
config: Ember.inject.service(),
@@ -62,24 +63,24 @@ export default Ember.Controller.extend({
}
}),
- actions: {
- validate: function () {
- this.get('model').validate(arguments);
- },
+ save: function () {
+ var notifications = this.get('notifications'),
+ config = this.get('config');
- save: function () {
- var notifications = this.get('notifications'),
- config = this.get('config');
+ return this.get('model').save().then(function (model) {
+ config.set('blogTitle', model.get('title'));
- return this.get('model').save().then(function (model) {
- config.set('blogTitle', model.get('title'));
+ return model;
+ }).catch(function (error) {
+ if (error) {
+ notifications.showAPIError(error);
+ }
+ });
+ },
- return model;
- }).catch(function (error) {
- if (error) {
- notifications.showAPIError(error);
- }
- });
+ actions: {
+ validate: function () {
+ this.get('model').validate(arguments);
},
checkPostsPerPage: function () {
diff --git a/core/client/app/controllers/settings/labs.js b/core/client/app/controllers/settings/labs.js
index c1e362957272..4cd9869e9068 100644
--- a/core/client/app/controllers/settings/labs.js
+++ b/core/client/app/controllers/settings/labs.js
@@ -4,6 +4,7 @@ import {request as ajax} from 'ic-ajax';
export default Ember.Controller.extend({
uploadButtonText: 'Import',
importErrors: '',
+ submitting: false,
ghostPaths: Ember.inject.service('ghost-paths'),
notifications: Ember.inject.service(),
@@ -78,18 +79,23 @@ export default Ember.Controller.extend({
},
sendTestEmail: function () {
- var notifications = this.get('notifications');
+ var notifications = this.get('notifications'),
+ self = this;
+
+ this.toggleProperty('submitting');
ajax(this.get('ghostPaths.url').api('mail', 'test'), {
type: 'POST'
}).then(function () {
notifications.showAlert('Check your email for the test message.', {type: 'info'});
+ self.toggleProperty('submitting');
}).catch(function (error) {
if (typeof error.jqXHR !== 'undefined') {
notifications.showAPIError(error);
} else {
notifications.showErrors(error);
}
+ self.toggleProperty('submitting');
});
}
}
diff --git a/core/client/app/controllers/settings/navigation.js b/core/client/app/controllers/settings/navigation.js
index 1807d0da6aa7..ff19c4ebcb82 100644
--- a/core/client/app/controllers/settings/navigation.js
+++ b/core/client/app/controllers/settings/navigation.js
@@ -1,4 +1,5 @@
import Ember from 'ember';
+import SettingsSaveMixin from 'ghost/mixins/settings-save';
var NavItem = Ember.Object.extend({
label: '',
@@ -10,7 +11,7 @@ var NavItem = Ember.Object.extend({
})
});
-export default Ember.Controller.extend({
+export default Ember.Controller.extend(SettingsSaveMixin, {
config: Ember.inject.service(),
notifications: Ember.inject.service(),
@@ -54,6 +55,65 @@ export default Ember.Controller.extend({
});
}),
+ save: function () {
+ var navSetting,
+ blogUrl = this.get('config').blogUrl,
+ blogUrlRegex = new RegExp('^' + blogUrl + '(.*)', 'i'),
+ navItems = this.get('navigationItems'),
+ message = 'One of your navigation items has an empty label. ' +
+ '
Please enter a new label or delete the item before saving.',
+ match,
+ notifications = this.get('notifications');
+
+ // Don't save if there's a blank label.
+ if (navItems.find(function (item) {return !item.get('isComplete') && !item.get('last');})) {
+ notifications.showAlert(message.htmlSafe(), {type: 'error'});
+ return;
+ }
+
+ navSetting = navItems.map(function (item) {
+ var label,
+ url;
+
+ if (!item || !item.get('isComplete')) {
+ return;
+ }
+
+ label = item.get('label').trim();
+ url = item.get('url').trim();
+
+ // is this an internal URL?
+ match = url.match(blogUrlRegex);
+
+ if (match) {
+ url = match[1];
+
+ // if the last char is not a slash, then add one,
+ // as long as there is no # or . in the URL (anchor or file extension)
+ // this also handles the empty case for the homepage
+ if (url[url.length - 1] !== '/' && url.indexOf('#') === -1 && url.indexOf('.') === -1) {
+ url += '/';
+ }
+ } else if (!validator.isURL(url) && url !== '' && url[0] !== '/' && url.indexOf('mailto:') !== 0) {
+ url = '/' + url;
+ }
+
+ return {label: label, url: url};
+ }).compact();
+
+ this.set('model.navigation', JSON.stringify(navSetting));
+
+ // trigger change event because even if the final JSON is unchanged
+ // we need to have navigationItems recomputed.
+ this.get('model').notifyPropertyChange('navigation');
+
+ notifications.closeNotifications();
+
+ return this.get('model').save().catch(function (err) {
+ notifications.showErrors(err);
+ });
+ },
+
actions: {
addItem: function () {
var navItems = this.get('navigationItems'),
@@ -94,65 +154,6 @@ export default Ember.Controller.extend({
}
navItem.set('url', url);
- },
-
- save: function () {
- var navSetting,
- blogUrl = this.get('config').blogUrl,
- blogUrlRegex = new RegExp('^' + blogUrl + '(.*)', 'i'),
- navItems = this.get('navigationItems'),
- message = 'One of your navigation items has an empty label. ' +
- '
Please enter a new label or delete the item before saving.',
- match,
- notifications = this.get('notifications');
-
- // Don't save if there's a blank label.
- if (navItems.find(function (item) {return !item.get('isComplete') && !item.get('last');})) {
- notifications.showAlert(message.htmlSafe(), {type: 'error'});
- return;
- }
-
- navSetting = navItems.map(function (item) {
- var label,
- url;
-
- if (!item || !item.get('isComplete')) {
- return;
- }
-
- label = item.get('label').trim();
- url = item.get('url').trim();
-
- // is this an internal URL?
- match = url.match(blogUrlRegex);
-
- if (match) {
- url = match[1];
-
- // if the last char is not a slash, then add one,
- // as long as there is no # or . in the URL (anchor or file extension)
- // this also handles the empty case for the homepage
- if (url[url.length - 1] !== '/' && url.indexOf('#') === -1 && url.indexOf('.') === -1) {
- url += '/';
- }
- } else if (!validator.isURL(url) && url !== '' && url[0] !== '/' && url.indexOf('mailto:') !== 0) {
- url = '/' + url;
- }
-
- return {label: label, url: url};
- }).compact();
-
- this.set('model.navigation', JSON.stringify(navSetting));
-
- // trigger change event because even if the final JSON is unchanged
- // we need to have navigationItems recomputed.
- this.get('model').notifyPropertyChange('navigation');
-
- notifications.closeNotifications();
-
- this.get('model').save().catch(function (err) {
- notifications.showErrors(err);
- });
}
}
});
diff --git a/core/client/app/controllers/setup/three.js b/core/client/app/controllers/setup/three.js
index bd0ed227fdec..00510a51cc27 100644
--- a/core/client/app/controllers/setup/three.js
+++ b/core/client/app/controllers/setup/three.js
@@ -9,6 +9,7 @@ export default Ember.Controller.extend({
users: '',
ownerEmail: Ember.computed.alias('two.email'),
+ submitting: false,
usersArray: Ember.computed('users', function () {
var users = this.get('users').split('\n').filter(function (email) {
return email.trim().length > 0;
@@ -75,6 +76,7 @@ export default Ember.Controller.extend({
this.get('errors').clear();
if (validationErrors === true && users.length > 0) {
+ this.toggleProperty('submitting');
this.get('authorRole').then(function (authorRole) {
Ember.RSVP.Promise.all(
users.map(function (user) {
@@ -123,6 +125,8 @@ export default Ember.Controller.extend({
self.send('loadServerNotifications');
self.transitionTo('posts.index');
}
+
+ self.toggleProperty('submitting');
});
});
} else if (users.length === 0) {
diff --git a/core/client/app/controllers/setup/two.js b/core/client/app/controllers/setup/two.js
index f703dd55a667..c506e723e1eb 100644
--- a/core/client/app/controllers/setup/two.js
+++ b/core/client/app/controllers/setup/two.js
@@ -11,6 +11,7 @@ export default Ember.Controller.extend(ValidationEngine, {
password: null,
image: null,
blogCreated: false,
+ submitting: false,
ghostPaths: Ember.inject.service('ghost-paths'),
notifications: Ember.inject.service(),
@@ -54,6 +55,8 @@ export default Ember.Controller.extend(ValidationEngine, {
config = this.get('config'),
method = this.get('blogCreated') ? 'PUT' : 'POST';
+ this.toggleProperty('submitting');
+
this.validate().then(function () {
self.set('showError', false);
ajax({
@@ -80,18 +83,23 @@ export default Ember.Controller.extend(ValidationEngine, {
if (data.image) {
self.sendImage(result.users[0])
.then(function () {
+ self.toggleProperty('submitting');
self.transitionToRoute('setup.three');
}).catch(function (resp) {
+ self.toggleProperty('submitting');
notifications.showAPIError(resp);
});
} else {
+ self.toggleProperty('submitting');
self.transitionToRoute('setup.three');
}
});
}).catch(function (resp) {
+ self.toggleProperty('submitting');
notifications.showAPIError(resp);
});
}).catch(function () {
+ self.toggleProperty('submitting');
self.set('showError', true);
});
},
diff --git a/core/client/app/controllers/signin.js b/core/client/app/controllers/signin.js
index 6a0cad34b8fe..20f084e67526 100644
--- a/core/client/app/controllers/signin.js
+++ b/core/client/app/controllers/signin.js
@@ -4,6 +4,7 @@ import {request as ajax} from 'ic-ajax';
export default Ember.Controller.extend(ValidationEngine, {
submitting: false,
+ loggingIn: false,
ghostPaths: Ember.inject.service('ghost-paths'),
notifications: Ember.inject.service(),
@@ -19,12 +20,15 @@ export default Ember.Controller.extend(ValidationEngine, {
authStrategy = 'simple-auth-authenticator:oauth2-password-grant',
data = model.getProperties('identification', 'password');
- this.get('session').authenticate(authStrategy, data).catch(function (err) {
+ this.get('session').authenticate(authStrategy, data).then(function () {
+ self.toggleProperty('loggingIn');
+ }).catch(function (err) {
+ self.toggleProperty('loggingIn');
+
if (err.errors) {
self.set('flowErrors', err.errors[0].message.string);
}
-
- // If authentication fails a rejected promise will be returned.
+ // if authentication fails a rejected promise will be returned.
// it needs to be caught so it doesn't generate an exception in the console,
// but it's actually "handled" by the sessionAuthenticationFailed action handler.
});
@@ -39,6 +43,7 @@ export default Ember.Controller.extend(ValidationEngine, {
this.validate().then(function () {
self.get('notifications').closeNotifications();
+ self.toggleProperty('loggingIn');
self.send('authenticate');
}).catch(function (error) {
if (error) {
@@ -56,7 +61,7 @@ export default Ember.Controller.extend(ValidationEngine, {
this.set('flowErrors', '');
this.validate({property: 'identification'}).then(function () {
- self.set('submitting', true);
+ self.toggleProperty('submitting');
ajax({
url: self.get('ghostPaths.url').api('authentication', 'passwordreset'),
@@ -67,10 +72,10 @@ export default Ember.Controller.extend(ValidationEngine, {
}]
}
}).then(function () {
- self.set('submitting', false);
+ self.toggleProperty('submitting');
notifications.showAlert('Please check your email for instructions.', {type: 'info'});
}).catch(function (resp) {
- self.set('submitting', false);
+ self.toggleProperty('submitting');
if (resp && resp.jqXHR && resp.jqXHR.responseJSON && resp.jqXHR.responseJSON.errors) {
self.set('flowErrors', resp.jqXHR.responseJSON.errors[0].message);
} else {
diff --git a/core/client/app/controllers/team/user.js b/core/client/app/controllers/team/user.js
index 0598d2bcf425..a52aaed442dd 100644
--- a/core/client/app/controllers/team/user.js
+++ b/core/client/app/controllers/team/user.js
@@ -7,6 +7,7 @@ import ValidationEngine from 'ghost/mixins/validation-engine';
export default Ember.Controller.extend(ValidationEngine, {
// ValidationEngine settings
validationType: 'user',
+ submitting: false,
ghostPaths: Ember.inject.service('ghost-paths'),
notifications: Ember.inject.service(),
@@ -103,6 +104,8 @@ export default Ember.Controller.extend(ValidationEngine, {
user.set('slug', slugValue);
}
+ this.toggleProperty('submitting');
+
promise = Ember.RSVP.resolve(afterUpdateSlug).then(function () {
return user.save({format: false});
}).then(function (model) {
@@ -121,11 +124,15 @@ export default Ember.Controller.extend(ValidationEngine, {
window.history.replaceState({path: newPath}, '', newPath);
}
+ this.toggleProperty('submitting');
+
return model;
}).catch(function (errors) {
if (errors) {
self.get('notifications').showErrors(errors);
}
+
+ this.toggleProperty('submitting');
});
this.set('lastPromise', promise);
diff --git a/core/client/app/mixins/editor-base-controller.js b/core/client/app/mixins/editor-base-controller.js
index 7fa44730f4af..bb1b5a21f4f1 100644
--- a/core/client/app/mixins/editor-base-controller.js
+++ b/core/client/app/mixins/editor-base-controller.js
@@ -17,6 +17,7 @@ export default Ember.Mixin.create({
autoSaveId: null,
timedSaveId: null,
editor: null,
+ submitting: false,
notifications: Ember.inject.service(),
@@ -247,6 +248,8 @@ export default Ember.Mixin.create({
options = options || {};
+ this.toggleProperty('submitting');
+
if (options.backgroundSave) {
// do not allow a post's status to be set to published by a background save
status = 'draft';
@@ -294,6 +297,7 @@ export default Ember.Mixin.create({
self.showSaveNotification(prevStatus, model.get('status'), isNew ? true : false);
}
+ self.toggleProperty('submitting');
return model;
});
}).catch(function (errors) {
@@ -303,6 +307,7 @@ export default Ember.Mixin.create({
self.set('model.status', prevStatus);
+ self.toggleProperty('submitting');
return self.get('model');
});
diff --git a/core/client/app/mixins/settings-save.js b/core/client/app/mixins/settings-save.js
new file mode 100644
index 000000000000..7341a142b154
--- /dev/null
+++ b/core/client/app/mixins/settings-save.js
@@ -0,0 +1,17 @@
+import Ember from 'ember';
+
+export default Ember.Mixin.create({
+ submitting: false,
+
+ actions: {
+ save: function () {
+ var self = this;
+
+ this.set('submitting', true);
+
+ this.save().then(function () {
+ self.set('submitting', false);
+ });
+ }
+ }
+});
diff --git a/core/client/app/styles/patterns/buttons.css b/core/client/app/styles/patterns/buttons.css
index e17b484948f2..232d90257413 100644
--- a/core/client/app/styles/patterns/buttons.css
+++ b/core/client/app/styles/patterns/buttons.css
@@ -200,3 +200,27 @@ input[type="reset"].btn-block,
input[type="button"].btn-block {
width: 100%;
}
+
+/* Spin Buttons!
+/* ---------------------------------------------------------- */
+.spinner {
+ position: relative;
+ display: inline-block;
+ box-sizing: border-box;
+ margin: -2px 0;
+ width: 14px;
+ height: 14px;
+ border: rgba(0,0,0,0.2) solid 4px;
+ border-radius: 100px;
+ animation: spin 1s linear infinite;
+}
+
+.spinner:before {
+ content: "";
+ display: block;
+ margin-top: 6px;
+ width: 4px;
+ height: 4px;
+ background: rgba(0,0,0,0.6);
+ border-radius: 100px;
+}
diff --git a/core/client/app/styles/patterns/global.css b/core/client/app/styles/patterns/global.css
index a74c823db950..2a5ddd9e9410 100644
--- a/core/client/app/styles/patterns/global.css
+++ b/core/client/app/styles/patterns/global.css
@@ -406,6 +406,15 @@ img {
}
}
+@keyframes spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
.fade-in {
animation: fade-in 0.2s;
animation-fill-mode: forwards;
diff --git a/core/client/app/templates/components/gh-editor-save-button.hbs b/core/client/app/templates/components/gh-editor-save-button.hbs
index 51af0661dbb2..7d938500b651 100644
--- a/core/client/app/templates/components/gh-editor-save-button.hbs
+++ b/core/client/app/templates/components/gh-editor-save-button.hbs
@@ -1,6 +1,4 @@
-
+{{gh-spin-button type="button" classNameBindings=":btn :btn-sm :js-publish-button isDangerous:btn-red:btn-blue" action="save" buttonText=saveText submitting=submitting}}
{{#gh-dropdown-button dropdownName="post-save-menu" classNameBindings=":btn :btn-sm isDangerous:btn-red:btn-blue btnopen:active :dropdown-toggle :up"}}
diff --git a/core/client/app/templates/components/gh-spin-button.hbs b/core/client/app/templates/components/gh-spin-button.hbs
new file mode 100644
index 000000000000..a18aecf6da39
--- /dev/null
+++ b/core/client/app/templates/components/gh-spin-button.hbs
@@ -0,0 +1,9 @@
+{{#unless submitting}}
+ {{#if buttonText}}
+ {{buttonText}}
+ {{else}}
+ {{{yield}}}
+ {{/if}}
+{{else}}
+
+{{/unless}}
diff --git a/core/client/app/templates/editor/edit.hbs b/core/client/app/templates/editor/edit.hbs
index 45b1867c2145..ac9b51263c98 100644
--- a/core/client/app/templates/editor/edit.hbs
+++ b/core/client/app/templates/editor/edit.hbs
@@ -16,6 +16,7 @@
save="save"
setSaveType="setSaveType"
delete="openDeleteModal"
+ submitting=submitting
}}
diff --git a/core/client/app/templates/modals/signin.hbs b/core/client/app/templates/modals/signin.hbs
index e33ce0124ca0..e60fb696a719 100644
--- a/core/client/app/templates/modals/signin.hbs
+++ b/core/client/app/templates/modals/signin.hbs
@@ -5,7 +5,7 @@
{{#if showError}}{{invalidMessage}}{{/if}}
diff --git a/core/client/app/templates/signin.hbs b/core/client/app/templates/signin.hbs index 2f31a328673f..8a2dd576b261 100644 --- a/core/client/app/templates/signin.hbs +++ b/core/client/app/templates/signin.hbs @@ -15,7 +15,7 @@ {{gh-error-message errors=model.errors property="password"}} {{/gh-form-group}} - + {{gh-spin-button class="login btn btn-blue btn-block" type="submit" tabindex="3" buttonText="Sign in" submitting=loggingIn autoWidth=false}}{{{flowErrors}}}
diff --git a/core/client/app/templates/signup.hbs b/core/client/app/templates/signup.hbs index de601d81c8e2..346ddaa7c006 100644 --- a/core/client/app/templates/signup.hbs +++ b/core/client/app/templates/signup.hbs @@ -48,7 +48,7 @@ {{/gh-form-group}} - + {{gh-spin-button type="submit" class="btn btn-green btn-lg btn-block" action="signup" submitting=submitting autoWidth=false}} diff --git a/core/client/app/templates/team/user.hbs b/core/client/app/templates/team/user.hbs index 24ffd7639218..420bb0f4f41b 100644 --- a/core/client/app/templates/team/user.hbs +++ b/core/client/app/templates/team/user.hbs @@ -30,7 +30,7 @@ {{/if}} - + {{gh-spin-button class="btn btn-blue" action="save" buttonText="Save" submitting=submitting}} diff --git a/core/client/tests/unit/components/gh-editor-save-button-test.js b/core/client/tests/unit/components/gh-editor-save-button-test.js index 85cc2b905d65..fb91697e33d9 100644 --- a/core/client/tests/unit/components/gh-editor-save-button-test.js +++ b/core/client/tests/unit/components/gh-editor-save-button-test.js @@ -12,6 +12,7 @@ describeComponent( needs: [ 'component:gh-dropdown-button', 'component:gh-dropdown', + 'component:gh-spin-button', 'service:dropdown' ] }, diff --git a/core/client/tests/unit/components/gh-spin-button-test.js b/core/client/tests/unit/components/gh-spin-button-test.js new file mode 100644 index 000000000000..33fb3c167e25 --- /dev/null +++ b/core/client/tests/unit/components/gh-spin-button-test.js @@ -0,0 +1,27 @@ +/* jshint expr:true */ +import {expect} from 'chai'; +import { + describeComponent, + it +} from 'ember-mocha'; + +describeComponent( + 'gh-spin-button', + 'GhSpinButtonComponent', + { + // 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'); + + // renders the component on the page + this.render(); + expect(component._state).to.equal('inDOM'); + }); + } +); diff --git a/core/test/functional/client/editor_test.js b/core/test/functional/client/editor_test.js index dc4df0a0e843..6e4c18221e7e 100644 --- a/core/test/functional/client/editor_test.js +++ b/core/test/functional/client/editor_test.js @@ -273,13 +273,13 @@ CasperTest.begin('Publish menu - new post', 10, function suite(test) { // Open the publish options menu; casper.thenClick('.js-publish-splitbutton .dropdown-toggle'); - casper.waitForOpaque('.js-publish-splitbutton .open'); + casper.waitForOpaque('.js-publish-splitbutton .dropdown-menu'); // Select the publish post button casper.thenClick('.js-publish-splitbutton li:first-child a'); // ... check status, label, class - casper.waitForSelector('.js-publish-splitbutton', function onSuccess() { + casper.waitForSelector('.js-publish-splitbutton .js-publish-button:not([disabled])', function onSuccess() { test.assertExists('.js-publish-button.btn-red', 'Publish button should have .btn-red'); test.assertSelectorHasText('.js-publish-button', 'Publish Now', '.js-publish-button says Publish Now'); }, function onTimeout() { @@ -291,7 +291,7 @@ CasperTest.begin('Publish menu - new post', 10, function suite(test) { casper.thenClick('.js-publish-button'); // ... check status, label, class - casper.waitForSelector('.js-publish-splitbutton', function onSuccess() { + casper.waitForSelector('.js-publish-splitbutton .js-publish-button:not([disabled])', function onSuccess() { test.assertExists('.js-publish-button.btn-blue', 'Update button should have .btn-blue'); test.assertSelectorHasText('.js-publish-button', 'Update Post', '.js-publish-button says Update Post'); }, function onTimeout() { @@ -329,13 +329,13 @@ CasperTest.begin('Publish menu - new page', 10, function suite(test) { // Open the publish options menu; casper.thenClick('.js-publish-splitbutton .dropdown-toggle'); - casper.waitForOpaque('.js-publish-splitbutton .open'); + casper.waitForOpaque('.js-publish-splitbutton .dropdown-menu'); // Select the publish post button casper.thenClick('.js-publish-splitbutton li:first-child a'); // ... check status, label, class - casper.waitForSelector('.js-publish-splitbutton', function onSuccess() { + casper.waitForSelector('.js-publish-splitbutton .js-publish-button:not([disabled])', function onSuccess() { test.assertExists('.js-publish-button.btn-red', 'Publish button should have .btn-red'); test.assertSelectorHasText('.js-publish-button', 'Publish Now', '.js-publish-button says Publish Now'); }, function onTimeout() { @@ -347,7 +347,7 @@ CasperTest.begin('Publish menu - new page', 10, function suite(test) { casper.thenClick('.js-publish-button'); // ... check status, label, class - casper.waitForSelector('.js-publish-splitbutton', function onSuccess() { + casper.waitForSelector('.js-publish-splitbutton .js-publish-button:not([disabled])', function onSuccess() { test.assertExists('.js-publish-button.btn-blue', 'Update button should have .btn-blue'); test.assertSelectorHasText('.js-publish-button', 'Update Page', '.js-publish-button says Update Page'); }, function onTimeout() { @@ -399,7 +399,7 @@ CasperTest.begin('Publish menu - existing post', 23, function suite(test) { casper.thenClick('.js-publish-splitbutton .dropdown-toggle'); - casper.waitForOpaque('.js-publish-splitbutton .open', function onSuccess() { + casper.waitForOpaque('.js-publish-splitbutton .dropdown-menu', function onSuccess() { test.assert(true, 'delete post button should be visible for saved drafts'); test.assertVisible( '.js-publish-splitbutton .delete', 'delete post button should be visible on saved drafts' @@ -412,7 +412,7 @@ CasperTest.begin('Publish menu - existing post', 23, function suite(test) { // Open the publish options menu; casper.thenClick('.js-publish-splitbutton .dropdown-toggle'); - casper.waitForOpaque('.js-publish-splitbutton .open'); + casper.waitForOpaque('.js-publish-splitbutton .dropdown-menu'); // Select the publish post button casper.thenClick('.js-publish-splitbutton li:first-child a'); @@ -445,7 +445,7 @@ CasperTest.begin('Publish menu - existing post', 23, function suite(test) { // Open the publish options menu; casper.thenClick('.js-publish-splitbutton .dropdown-toggle'); - casper.waitForOpaque('.js-publish-splitbutton .open'); + casper.waitForOpaque('.js-publish-splitbutton .dropdown-menu'); // Select the publish post button casper.thenClick('.js-publish-splitbutton li:nth-child(2) a'); @@ -488,7 +488,7 @@ CasperTest.begin('Publish menu - delete post', 6, function testDeleteModal(test) // Open post settings menu casper.thenClick('.js-publish-splitbutton .dropdown-toggle'); - casper.waitForOpaque('.js-publish-splitbutton .open'); + casper.waitForOpaque('.js-publish-splitbutton .dropdown-menu'); casper.thenClick('.js-publish-splitbutton li:nth-child(4) a'); casper.waitUntilVisible('.modal-container', function onSuccess() { @@ -506,7 +506,7 @@ CasperTest.begin('Publish menu - delete post', 6, function testDeleteModal(test) // Test delete casper.thenClick('.js-publish-splitbutton .dropdown-toggle'); - casper.waitForOpaque('.js-publish-splitbutton .open'); + casper.waitForOpaque('.js-publish-splitbutton .dropdown-menu'); casper.thenClick('.js-publish-splitbutton li:nth-child(4) a'); casper.waitForSelector('.modal-container .modal-content', function onSuccess() { @@ -536,7 +536,7 @@ CasperTest.begin('Publish menu - new post status is correct after failed save', // Open the publish options menu; casper.thenClick('.js-publish-splitbutton .dropdown-toggle'); - casper.waitForOpaque('.js-publish-splitbutton .open'); + casper.waitForOpaque('.js-publish-splitbutton .dropdown-menu'); // Select the publish post button casper.thenClick('.js-publish-splitbutton li:first-child a'); @@ -581,7 +581,7 @@ CasperTest.begin('Publish menu - existing post status is correct after failed sa // Open the publish options menu; casper.thenClick('.js-publish-splitbutton .dropdown-toggle'); - casper.waitForOpaque('.js-publish-splitbutton .open'); + casper.waitForOpaque('.js-publish-splitbutton .dropdown-menu'); // Select the publish post button casper.thenClick('.js-publish-splitbutton li:first-child a');