From 1bcd7fd333f7ff1e615d80297ebf84cd948a83c2 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Tue, 7 Jul 2015 18:14:23 +0100 Subject: [PATCH] Replace validation notifications with inline validations issue #5409 & #5336 - update settings/general - update signin - update signup - update edit user - update reset password - update setup/three - remove `formatErrors` function from validationEngine mixin (it's no longer needed as inline validations should handle this instead) --- .../app/components/gh-trim-focus-input.js | 9 ++- .../app/controllers/settings/general.js | 10 +++- core/client/app/controllers/setup/three.js | 15 +++-- core/client/app/controllers/signin.js | 50 ++++++++--------- core/client/app/controllers/signup.js | 11 ++-- core/client/app/controllers/team/user.js | 6 +- core/client/app/mixins/validation-engine.js | 56 ++----------------- core/client/app/styles/layouts/flow.css | 1 + core/client/app/templates/reset.hbs | 21 +++++-- .../client/app/templates/settings/general.hbs | 24 ++++---- core/client/app/templates/setup/three.hbs | 1 + core/client/app/templates/signin.hbs | 14 +++-- core/client/app/templates/signup.hbs | 23 ++++---- core/client/app/templates/team/user.hbs | 47 ++++++++++------ core/client/app/validators/reset.js | 5 +- core/client/app/validators/setting.js | 2 +- core/client/app/validators/signin.js | 1 + core/client/app/validators/user.js | 5 +- core/test/functional/client/editor_test.js | 3 +- core/test/functional/client/settings_test.js | 29 +++++----- core/test/functional/client/signin_test.js | 21 +++---- core/test/functional/client/team_test.js | 46 +++++++-------- core/test/functional/setup/setup_test.js | 30 +++++----- 23 files changed, 208 insertions(+), 222 deletions(-) diff --git a/core/client/app/components/gh-trim-focus-input.js b/core/client/app/components/gh-trim-focus-input.js index b75f4f9844..f1627bf7f6 100644 --- a/core/client/app/components/gh-trim-focus-input.js +++ b/core/client/app/components/gh-trim-focus-input.js @@ -13,19 +13,18 @@ var TrimFocusInput = Ember.TextField.extend({ return false; }), - didInsertElement: function () { + focusField: Ember.on('didInsertElement', function () { // This fix is required until Mobile Safari has reliable // autofocus, select() or focus() support if (this.get('focus') && !device.ios()) { this.$().val(this.$().val()).focus(); } - }, + }), - focusOut: function () { + trimValue: Ember.on('focusOut', function () { var text = this.$().val(); - this.$().val(text.trim()); - } + }) }); export default TrimFocusInput; diff --git a/core/client/app/controllers/settings/general.js b/core/client/app/controllers/settings/general.js index 0a97ad1b8d..68a67779a9 100644 --- a/core/client/app/controllers/settings/general.js +++ b/core/client/app/controllers/settings/general.js @@ -63,6 +63,10 @@ export default Ember.Controller.extend({ }), actions: { + validate: function () { + this.get('model').validate(arguments); + }, + save: function () { var notifications = this.get('notifications'), config = this.get('config'); @@ -71,8 +75,10 @@ export default Ember.Controller.extend({ config.set('blogTitle', model.get('title')); return model; - }).catch(function (errors) { - notifications.showErrors(errors); + }).catch(function (error) { + if (error) { + notifications.showAPIError(error); + } }); }, diff --git a/core/client/app/controllers/setup/three.js b/core/client/app/controllers/setup/three.js index 5c8fb1ab7f..4c56a58d96 100644 --- a/core/client/app/controllers/setup/three.js +++ b/core/client/app/controllers/setup/three.js @@ -1,7 +1,9 @@ import Ember from 'ember'; +import DS from 'ember-data'; export default Ember.Controller.extend({ notifications: Ember.inject.service(), + errors: DS.Errors.create(), users: '', usersArray: Ember.computed('users', function () { var users = this.get('users').split('\n').filter(function (email) { @@ -62,10 +64,11 @@ export default Ember.Controller.extend({ var self = this, validationErrors = this.get('validateUsers'), users = this.get('usersArray'), - errorMessages, notifications = this.get('notifications'), invitationsString; + this.get('errors').clear(); + if (validationErrors === true && users.length > 0) { this.get('authorRole').then(function (authorRole) { Ember.RSVP.Promise.all( @@ -117,19 +120,15 @@ export default Ember.Controller.extend({ }); }); } else if (users.length === 0) { - // TODO: switch to inline-validation - notifications.showAlert('No users to invite.', {type: 'error'}); + this.get('errors').add('users', 'No users to invite.'); } else { - errorMessages = validationErrors.map(function (error) { + validationErrors.forEach(function (error) { // Only one error type here so far, but one day the errors might be more detailed switch (error.error) { case 'email': - return {message: error.user + ' is not a valid email.'}; + self.get('errors').add('users', error.user + ' is not a valid email.'); } }); - - // TODO: switch to inline-validation - notifications.showErrors(errorMessages); } } } diff --git a/core/client/app/controllers/signin.js b/core/client/app/controllers/signin.js index b74494950a..76c352f915 100644 --- a/core/client/app/controllers/signin.js +++ b/core/client/app/controllers/signin.js @@ -3,13 +3,14 @@ import ValidationEngine from 'ghost/mixins/validation-engine'; import {request as ajax} from 'ic-ajax'; export default Ember.Controller.extend(ValidationEngine, { - validationType: 'signin', - submitting: false, ghostPaths: Ember.inject.service('ghost-paths'), notifications: Ember.inject.service(), + // ValidationEngine settings + validationType: 'signin', + actions: { authenticate: function () { var model = this.get('model'), @@ -30,12 +31,12 @@ export default Ember.Controller.extend(ValidationEngine, { // browsers and password managers that don't send proper events on autofill $('#login').find('input').trigger('change'); - this.validate({format: false}).then(function () { + this.validate().then(function () { self.get('notifications').closeNotifications(); self.send('authenticate'); - }).catch(function (errors) { - if (errors) { - self.get('notifications').showErrors(errors); + }).catch(function (error) { + if (error) { + self.get('notifications').showAPIError(error); } }); }, @@ -45,27 +46,24 @@ export default Ember.Controller.extend(ValidationEngine, { notifications = this.get('notifications'), self = this; - if (!email) { - // TODO: Switch to in-line validation - return notifications.showNotification('Enter email address to reset password.', {type: 'error'}); - } + this.validate({property: 'identification'}).then(function () { + self.set('submitting', true); - self.set('submitting', true); - - ajax({ - url: self.get('ghostPaths.url').api('authentication', 'passwordreset'), - type: 'POST', - data: { - passwordreset: [{ - email: email - }] - } - }).then(function () { - self.set('submitting', false); - 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.'}); + ajax({ + url: self.get('ghostPaths.url').api('authentication', 'passwordreset'), + type: 'POST', + data: { + passwordreset: [{ + email: email + }] + } + }).then(function () { + self.set('submitting', false); + 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.'}); + }); }); } } diff --git a/core/client/app/controllers/signup.js b/core/client/app/controllers/signup.js index cbb981d5d7..b348109dda 100644 --- a/core/client/app/controllers/signup.js +++ b/core/client/app/controllers/signup.js @@ -20,8 +20,8 @@ export default Ember.Controller.extend(ValidationEngine, { notifications.closeNotifications(); - this.toggleProperty('submitting'); - this.validate({format: false}).then(function () { + this.validate().then(function () { + this.toggleProperty('submitting'); ajax({ url: self.get('ghostPaths.url').api('authentication', 'invitation'), type: 'POST', @@ -43,10 +43,9 @@ export default Ember.Controller.extend(ValidationEngine, { self.toggleProperty('submitting'); notifications.showAPIError(resp); }); - }).catch(function (errors) { - self.toggleProperty('submitting'); - if (errors) { - notifications.showErrors(errors); + }).catch(function (error) { + if (error) { + notifications.showAPIError(error); } }); } diff --git a/core/client/app/controllers/team/user.js b/core/client/app/controllers/team/user.js index 3cb9032a16..0598d2bcf4 100644 --- a/core/client/app/controllers/team/user.js +++ b/core/client/app/controllers/team/user.js @@ -2,8 +2,12 @@ import Ember from 'ember'; import SlugGenerator from 'ghost/models/slug-generator'; import isNumber from 'ghost/utils/isNumber'; import boundOneWay from 'ghost/utils/bound-one-way'; +import ValidationEngine from 'ghost/mixins/validation-engine'; + +export default Ember.Controller.extend(ValidationEngine, { + // ValidationEngine settings + validationType: 'user', -export default Ember.Controller.extend({ ghostPaths: Ember.inject.service('ghost-paths'), notifications: Ember.inject.service(), diff --git a/core/client/app/mixins/validation-engine.js b/core/client/app/mixins/validation-engine.js index 3c4287588f..be1370cfc4 100644 --- a/core/client/app/mixins/validation-engine.js +++ b/core/client/app/mixins/validation-engine.js @@ -15,49 +15,6 @@ import TagSettingsValidator from 'ghost/validators/tag-settings'; // our extensions to the validator library ValidatorExtensions.init(); -// This is here because it is used by some things that format errors from api responses -// This function should be removed in the notifications refactor -// format errors to be used in `notifications.showErrors`. -// result is [{message: 'concatenated error messages'}] -function formatErrors(errors, opts) { - var message = 'There was an error'; - - opts = opts || {}; - - if (opts.wasSave && opts.validationType) { - message += ' saving this ' + opts.validationType; - } - - if (Ember.isArray(errors)) { - // get the validator's error messages from the array. - // normalize array members to map to strings. - message = errors.map(function (error) { - var errorMessage; - if (typeof error === 'string') { - errorMessage = error; - } else { - errorMessage = error.message; - } - - return Ember.Handlebars.Utils.escapeExpression(errorMessage); - }).join('
').htmlSafe(); - } else if (errors instanceof Error) { - message += errors.message || '.'; - } else if (typeof errors === 'object') { - // Get messages from server response - message += ': ' + getRequestErrorMessage(errors, true); - } else if (typeof errors === 'string') { - message += ': ' + errors; - } else { - message += '.'; - } - - // set format for notifications.showErrors - message = [{message: message}]; - - return message; -} - /** * The class that gets this mixin will receive these properties and functions. * It will be able to validate any properties on itself (or the model it passes to validate()) @@ -163,15 +120,10 @@ export default Ember.Mixin.create({ return this.validate(options).then(function () { return _super.call(self, options); }).catch(function (result) { - // server save failed - validate() would have given back an array - if (!Ember.isArray(result)) { - if (options.format !== false) { - // concatenate all errors into an array with a single object: [{message: 'concatted message'}] - result = formatErrors(result, options); - } else { - // return the array of errors from the server - result = getRequestErrorMessage(result); - } + // server save failed or validator type doesn't exist + if (result && !Ember.isArray(result)) { + // return the array of errors from the server + result = getRequestErrorMessage(result); } return Ember.RSVP.reject(result); diff --git a/core/client/app/styles/layouts/flow.css b/core/client/app/styles/layouts/flow.css index fd880b17ba..9e019741bb 100644 --- a/core/client/app/styles/layouts/flow.css +++ b/core/client/app/styles/layouts/flow.css @@ -377,6 +377,7 @@ } .gh-flow-content .gh-flow-invite { + position: relative; margin: 0 auto; max-width: 400px; width: 100%; diff --git a/core/client/app/templates/reset.hbs b/core/client/app/templates/reset.hbs index 3e98a6ca13..b630b5e532 100644 --- a/core/client/app/templates/reset.hbs +++ b/core/client/app/templates/reset.hbs @@ -2,12 +2,21 @@
diff --git a/core/client/app/templates/settings/general.hbs b/core/client/app/templates/settings/general.hbs index d33fbe7b0d..5ea6b51960 100644 --- a/core/client/app/templates/settings/general.hbs +++ b/core/client/app/templates/settings/general.hbs @@ -10,21 +10,22 @@
-
+ {{#gh-form-group errors=model.errors property="title"}} - {{input id="blog-title" class="gh-input" name="general[title]" type="text" value=model.title}} + {{gh-input id="blog-title" class="gh-input" name="general[title]" type="text" value=model.title focusOut=(action "validate" "title")}} + {{gh-error-message errors=model.errors property="title"}}

The name of your blog

-
+ {{/gh-form-group}} -
+ {{#gh-form-group class="description-container" errors=model.errors property="description"}} - {{textarea id="blog-description" class="gh-input" name="general[description]" value=model.description}} + {{gh-textarea id="blog-description" class="gh-input" name="general[description]" value=model.description focusOut=(action "validate" "description")}} + {{gh-error-message errors=model.errors property="description"}}

Describe what your blog is about {{gh-count-characters model.description}}

- -
+ {{/gh-form-group}}
@@ -52,7 +53,7 @@
{{! `pattern` brings up numeric keypad allowing any number of digits}} - {{input id="postsPerPage" class="gh-input" name="general[postsPerPage]" focus-out="checkPostsPerPage" value=model.postsPerPage min="1" max="1000" type="number" pattern="[0-9]*"}} + {{gh-input id="postsPerPage" class="gh-input" name="general[postsPerPage]" focus-out="checkPostsPerPage" value=model.postsPerPage min="1" max="1000" type="number" pattern="[0-9]*"}}

How many posts should be displayed on each page

@@ -92,10 +93,11 @@
{{#if model.isPrivate}} -
- {{input name="general[password]" type="text" value=model.password}} + {{#gh-form-group errors=model.errors property="password"}} + {{gh-input name="general[password]" type="text" value=model.password focusOut=(action "validate" "password")}} + {{gh-error-message errors=model.errors property="password"}}

This password will be needed to access your blog. All search engine optimization and social features are now disabled. This password is stored in plaintext.

-
+ {{/gh-form-group}} {{/if}}
diff --git a/core/client/app/templates/setup/three.hbs b/core/client/app/templates/setup/three.hbs index 9b57f21eae..ce9359d4db 100644 --- a/core/client/app/templates/setup/three.hbs +++ b/core/client/app/templates/setup/three.hbs @@ -8,6 +8,7 @@
{{textarea class="gh-input" name="users" value=users required="required"}} + {{gh-error-message errors=errors property="users"}}
-
+ {{gh-error-message errors=model.errors property="password"}} + {{/gh-form-group}} diff --git a/core/client/app/templates/signup.hbs b/core/client/app/templates/signup.hbs index 374f959f6b..de601d81c8 100644 --- a/core/client/app/templates/signup.hbs +++ b/core/client/app/templates/signup.hbs @@ -14,26 +14,28 @@
- User imge + User image
-
+ {{#gh-form-group errors=model.errors property="email"}} - {{input class="gh-input" type="email" name="email" autocorrect="off" value=model.email }} + {{gh-input type="email" name="email" placeholder="Eg. john@example.com" class="gh-input" autofocus="autofocus" autocorrect="off" value=model.email focusOut=(action "validate" "email")}} -
-
+ {{gh-error-message errors=model.errors property="email"}} + {{/gh-form-group}} + {{#gh-form-group errors=model.errors property="name"}} - {{gh-trim-focus-input class="gh-input" type="text" name="name" autofocus="autofocus" autocorrect="off" value=model.name }} + {{gh-input type="text" name="name" placeholder="Eg. John H. Watson" class="gh-input" autofocus="autofocus" autocorrect="off" value=model.name focusOut=(action "validate" "name")}} -
-
+ {{gh-error-message errors=model.errors property="name"}} + {{/gh-form-group}} + {{#gh-form-group errors=model.errors property="password"}} - {{input class="gh-input" type="password" name="password" autofocus="autofocus" autocorrect="off" value=model.password }} + {{input class="gh-input" type="password" name="password" autofocus="autofocus" autocorrect="off" value=model.password focusOut=(action "validate" "password")}}
@@ -42,7 +44,8 @@
-
+ {{gh-error-message errors=model.errors property="password"}} + {{/gh-form-group}} diff --git a/core/client/app/templates/team/user.hbs b/core/client/app/templates/team/user.hbs index 00a18cef51..24ffd76392 100644 --- a/core/client/app/templates/team/user.hbs +++ b/core/client/app/templates/team/user.hbs @@ -53,32 +53,39 @@ -
+ {{#gh-form-group class="first-form-group" errors=user.errors property="name"}} - {{input value=user.name id="user-name" class="gh-input user-name" placeholder="Full Name" autocorrect="off"}} -

Use your real name so people can recognise you

-
+ {{input value=user.name id="user-name" class="gh-input user-name" placeholder="Full Name" autocorrect="off" focusOut=(action "validate" "name")}} + {{#if user.errors.name}} + {{gh-error-message errors=user.errors property="name"}} + {{else}} +

Use your real name so people can recognise you

+ {{/if}} + {{/gh-form-group}}
-
+ {{#gh-form-group errors=user.errors property="slug"}} {{gh-input class="gh-input user-name" id="user-slug" value=slugValue name="user" focus-out="updateSlug" placeholder="Slug" selectOnClick="true" autocorrect="off"}}

{{gh-blog-url}}/author/{{slugValue}}

-
+ {{gh-error-message errors=user.errors property="slug"}} + {{/gh-form-group}} -
+ {{#gh-form-group errors=user.errors property="email"}} {{!-- Administrators only see text of Owner's email address but not input --}} {{#unless isAdminUserOnOwnerProfile}} - {{input type="email" value=user.email id="user-email" class="gh-input" placeholder="Email Address" autocapitalize="off" autocorrect="off" autocomplete="off"}} + {{input type="email" value=user.email id="user-email" name="email" class="gh-input" placeholder="Email Address" autocapitalize="off" autocorrect="off" autocomplete="off" focusOut=(action "validate" "email")}} + {{gh-error-message errors=user.errors property="email"}} {{else}} {{user.email}} {{/unless}}

Used for notifications

-
+ {{/gh-form-group}} + {{#if rolesDropdownIsVisible}}
@@ -94,26 +101,30 @@

What permissions should this user have?

{{/if}} -
+ + {{#gh-form-group errors=user.errors property="location"}} - {{input type="text" value=user.location id="user-location" class="gh-input"}} + {{input type="text" value=user.location id="user-location" class="gh-input" focusOut=(action "validate" "location")}} + {{gh-error-message errors=user.errors property="location"}}

Where in the world do you live?

-
+ {{/gh-form-group}} -
+ {{#gh-form-group errors=user.errors property="website"}} - {{input type="url" value=user.website id="user-website" class="gh-input" autocapitalize="off" autocorrect="off" autocomplete="off"}} + {{input type="url" value=user.website id="user-website" class="gh-input" autocapitalize="off" autocorrect="off" autocomplete="off" focusOut=(action "validate" "website")}} + {{gh-error-message errors=user.errors property="website"}}

Have a website or blog other than this one? Link it!

-
+ {{/gh-form-group}} -
+ {{#gh-form-group class="bio-container" errors=user.errors property="bio"}} - {{textarea id="user-bio" class="gh-input" value=user.bio}} + {{textarea id="user-bio" class="gh-input" value=user.bio focusOut=(action "validate" "bio")}} + {{gh-error-message errors=user.errors property="bio"}}

Write about you, in 200 characters or less. {{gh-count-characters user.bio}}

-
+ {{/gh-form-group}}
diff --git a/core/client/app/validators/reset.js b/core/client/app/validators/reset.js index b69cd856cd..d80a675afe 100644 --- a/core/client/app/validators/reset.js +++ b/core/client/app/validators/reset.js @@ -5,7 +5,10 @@ var ResetValidator = BaseValidator.create({ var p1 = model.get('newPassword'), p2 = model.get('ne2Password'); - if (!validator.isLength(p1, 8)) { + if (validator.empty(p1)) { + model.get('errors').add('newPassword', 'Please enter a password.'); + this.invalidate(); + } else if (!validator.isLength(p1, 8)) { model.get('errors').add('newPassword', 'The password is not long enough.'); this.invalidate(); } else if (!validator.equals(p1, p2)) { diff --git a/core/client/app/validators/setting.js b/core/client/app/validators/setting.js index b0795a5f1d..44e6a29c95 100644 --- a/core/client/app/validators/setting.js +++ b/core/client/app/validators/setting.js @@ -20,7 +20,7 @@ var SettingValidator = BaseValidator.create({ }, password: function (model) { var isPrivate = model.get('isPrivate'), - password = this.get('password'); + password = model.get('password'); if (isPrivate && password === '') { model.get('errors').add('password', 'Password must be supplied'); diff --git a/core/client/app/validators/signin.js b/core/client/app/validators/signin.js index 868509ed13..b44350922d 100644 --- a/core/client/app/validators/signin.js +++ b/core/client/app/validators/signin.js @@ -7,6 +7,7 @@ var SigninValidator = BaseValidator.create({ if (validator.empty(id)) { model.get('errors').add('identification', 'Please enter an email'); + this.invalidate(); } else if (!validator.isEmail(id)) { model.get('errors').add('identification', 'Invalid email'); this.invalidate(); diff --git a/core/client/app/validators/user.js b/core/client/app/validators/user.js index 9b615452c0..4f842eb49f 100644 --- a/core/client/app/validators/user.js +++ b/core/client/app/validators/user.js @@ -10,7 +10,10 @@ var UserValidator = BaseValidator.create({ var name = model.get('name'); if (this.isActive(model)) { - if (!validator.isLength(name, 0, 150)) { + if (validator.empty(name)) { + model.get('errors').add('name', 'Please enter a name.'); + this.invalidate(); + } else if (!validator.isLength(name, 0, 150)) { model.get('errors').add('name', 'Name is too long'); this.invalidate(); } diff --git a/core/test/functional/client/editor_test.js b/core/test/functional/client/editor_test.js index e647b27bf6..dc4df0a0e8 100644 --- a/core/test/functional/client/editor_test.js +++ b/core/test/functional/client/editor_test.js @@ -556,8 +556,7 @@ CasperTest.begin('Publish menu - new post status is correct after failed save', }); }); -// TODO: Change number of tests back to 6 once the commented-out tests are fixed -CasperTest.begin('Publish menu - existing post status is correct after failed save', 4, function suite(test) { +CasperTest.begin('Publish menu - existing post status is correct after failed save', 6, 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'); diff --git a/core/test/functional/client/settings_test.js b/core/test/functional/client/settings_test.js index 5f237dd3b6..2e5128c899 100644 --- a/core/test/functional/client/settings_test.js +++ b/core/test/functional/client/settings_test.js @@ -76,8 +76,7 @@ CasperTest.begin('General settings pane is correct', 4, function suite(test) { }); // ## General settings validations tests -// // TODO: Change number of tests back to 6 once the commented-out tests are fixed -CasperTest.begin('General settings validation is correct', 4, function suite(test) { +CasperTest.begin('General settings validation is correct', 7, function suite(test) { casper.thenOpenAndWaitForPageLoad('settings.general', function testTitleAndUrl() { test.assertTitle('Settings - General - Test Blog', 'Ghost admin has incorrect title'); test.assertUrlMatch(/ghost\/settings\/general\/$/, 'Landed on the correct URL'); @@ -88,25 +87,19 @@ CasperTest.begin('General settings validation is correct', 4, function suite(tes 'general[title]': new Array(152).join('a') }); - // 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-alert-close'); + casper.waitForText('Title is too long', function onSuccess() { + test.assert(true, 'Blog title length error was shown'); + }, casper.failOnTimeout(test, 'Blog title length error did not appear')); // Ensure general blog description field length validation casper.fillAndSave('form#settings-general', { 'general[description]': new Array(202).join('a') }); - // 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.waitForText('Description is too long', function onSuccess() { + test.assert(true, 'Blog description length error was shown'); }, casper.failOnTimeout(test, 'Blog description length error did not appear')); - 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 casper.fillAndSave('form#settings-general', { @@ -128,4 +121,14 @@ CasperTest.begin('General settings validation is correct', 4, function suite(tes casper.then(function checkSlugInputValue() { test.assertField('general[postsPerPage]', '5', 'posts per page is set correctly'); }); + + // Ensure private blog password validation + casper.fillAndSave('form#settings-general', { + 'general[isPrivate]': '1', + 'general[password]': '' + }); + + casper.waitForText('Password must be supplied', function onSuccess() { + test.assert(true, 'Password required error was shown'); + }, casper.failOnTimeout(test, 'Password required error did not appear')); }); diff --git a/core/test/functional/client/signin_test.js b/core/test/functional/client/signin_test.js index 67e2f0af59..551766ec94 100644 --- a/core/test/functional/client/signin_test.js +++ b/core/test/functional/client/signin_test.js @@ -110,8 +110,7 @@ CasperTest.begin('Authenticated user is redirected', 6, function suite(test) { }); }, true); -// TODO: Change number of tests back to 4 once the commented-out tests are fixed -CasperTest.begin('Ensure email field form validation', 2, function suite(test) { +CasperTest.begin('Ensure email field form validation', 4, function suite(test) { CasperTest.Routines.signout.run(test); casper.thenOpenAndWaitForPageLoad('signin', function testTitleAndUrl() { @@ -129,12 +128,9 @@ CasperTest.begin('Ensure email field form validation', 2, function suite(test) { test.fail('Login form didn\'t fade in.'); }); - // 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.waitForText('Invalid email', function onSuccess() { + test.assert(true, 'Invalid email error was shown'); + }, casper.failOnTimeout(test, 'Invalid email error was not shown')); casper.then(function testMissingEmail() { this.fillAndSave('form.gh-signin', { @@ -142,10 +138,7 @@ CasperTest.begin('Ensure email field form validation', 2, function suite(test) { }); }); - // 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); + casper.waitForText('Please enter an email', function onSuccess() { + test.assert(true, 'Missing email error was shown'); + }, casper.failOnTimeout(test, 'Missing email error was not shown')); }, true); diff --git a/core/test/functional/client/team_test.js b/core/test/functional/client/team_test.js index 2d17942a06..2d2b57fa5b 100644 --- a/core/test/functional/client/team_test.js +++ b/core/test/functional/client/team_test.js @@ -192,7 +192,7 @@ CasperTest.begin('User settings screen change slug handles duplicate slug', 4, f }); }); -CasperTest.begin('User settings screen validates email', 6, function suite(test) { +CasperTest.begin('User settings screen validates email', 4, function suite(test) { var email; casper.thenOpenAndWaitForPageLoad('team.user', function testTitleAndUrl() { @@ -208,21 +208,14 @@ CasperTest.begin('User settings screen validates email', 6, function suite(test) casper.then(function setEmailToInvalid() { var brokenEmail = email.replace('.', '-'); - - casper.fillSelectors('.user-profile', { - '#user-email': brokenEmail - }, false); + this.fillAndSave('.user-profile', { + email: brokenEmail + }); }); - casper.thenClick('.btn-blue'); - - casper.waitForResource('/team/'); - - // 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.waitForText('Please supply a valid email address', function onSuccess() { + test.assert(true, 'Invalid email error was shown'); + }, casper.failOnTimeout(test, 'Invalid email error was not shown')); casper.then(function resetEmailToValid() { casper.fillSelectors('.user-profile', { @@ -230,6 +223,10 @@ CasperTest.begin('User settings screen validates email', 6, function suite(test) }, false); }); + casper.then(function checkEmailErrorWasCleared() { + test.assertTextDoesntExist('Please supply a valid email address', 'Invalid email error was not cleared'); + }); + casper.thenClick('.view-actions .btn-blue'); casper.waitForResource(/users/); @@ -275,10 +272,9 @@ CasperTest.begin('Ensure user bio field length validation', 3, function suite(te casper.thenClick('.view-actions .btn-blue'); - // 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)); + casper.waitForText('Bio is too long', function onSuccess() { + test.assert(true, 'Bio too long error was shown'); + }, casper.failOnTimeout(test, 'Bio too long error was not shown')); }); CasperTest.begin('Ensure user url field validation', 3, function suite(test) { @@ -295,10 +291,9 @@ CasperTest.begin('Ensure user url field validation', 3, function suite(test) { casper.thenClick('.view-actions .btn-blue'); - // 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)); + casper.waitForText('Website is not a valid url', function onSuccess() { + test.assert(true, 'Website invalid error was shown'); + }, casper.failOnTimeout(test, 'Website invalid error was not shown')); }); CasperTest.begin('Ensure user location field length validation', 3, function suite(test) { @@ -315,8 +310,7 @@ CasperTest.begin('Ensure user location field length validation', 3, function sui casper.thenClick('.view-actions .btn-blue'); - // 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)); + casper.waitForText('Location is too long', function onSuccess() { + test.assert(true, 'Location too long error was shown'); + }, casper.failOnTimeout(test, 'Location too long error was not shown')); }); diff --git a/core/test/functional/setup/setup_test.js b/core/test/functional/setup/setup_test.js index e2a0f91345..544cf09558 100644 --- a/core/test/functional/setup/setup_test.js +++ b/core/test/functional/setup/setup_test.js @@ -2,7 +2,7 @@ /*global CasperTest, casper, email, user, password */ -CasperTest.begin('Ghost setup fails properly', 12, function suite(test) { +CasperTest.begin('Ghost setup fails properly', 11, function suite(test) { casper.thenOpenAndWaitForPageLoad('setup', function then() { test.assertUrlMatch(/ghost\/setup\/one\/$/, 'Landed on the correct URL'); }); @@ -11,14 +11,10 @@ CasperTest.begin('Ghost setup fails properly', 12, function suite(test) { casper.fillAndAdd('#setup', {'blog-title': 'ghost', name: 'slimer', email: email, password: 'short'}); }); - // TODO: Fix tests to support inline validation - // should now throw a short password error - 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 :('); - }); + // should now show a short password error + casper.waitForText('Password must be at least 8 characters long', function onSuccess() { + test.assert(true, 'Short password error was shown'); + }, casper.failOnTimeout(test, 'Short password error was not shown')); casper.then(function setupWithLongPassword() { casper.fillAndAdd('#setup', {'blog-title': 'ghost', name: 'slimer', email: email, password: password}); @@ -31,16 +27,24 @@ CasperTest.begin('Ghost setup fails properly', 12, function suite(test) { casper.thenClick('.gh-flow-content .btn'); }); - casper.waitForSelector('.gh-alert', function onSuccess() { - test.assert(true, 'Got error notification'); - test.assertSelectorHasText('.gh-alert', 'No users to invite.'); + casper.waitForText('No users to invite.', function onSuccess() { + test.assert(true, 'Got error message'); 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'); }, function onTimeout() { - test.assert(false, 'No error notification for empty invitation list'); + test.assert(false, 'No error message for empty invitation list'); }); + casper.then(function fillInvalidEmail() { + casper.fill('form.gh-flow-invite', {users: 'test'}); + casper.thenClick('.gh-flow-content .btn'); + }); + + casper.waitForText('test is not a valid email.', function onSuccess() { + test.assert(true, 'Got invalid email error'); + }, casper.failOnTimeout(test, 'Invalid email error not shown')); + casper.then(function fillInvitationForm() { casper.fill('form.gh-flow-invite', {users: 'test@example.com'}); test.assertSelectorHasText('.gh-flow-content .btn', 'Invite 1 user', 'One invitation button text is incorrect');