Fix blank signup screen (#135)

closes https://github.com/TryGhost/Ghost/issues/7117
- adds guard to `sanitizeInput` method of `gh-trim-focus-input` for null/undefined values
- adds acceptance test for successful signup screen flow
- removes unneeded validation/update handling for a non-editable email field
- adds "At least 8 characters" placeholder to password field
- fixes enter key not submitting the form when name or password field has focus
This commit is contained in:
Kevin Ansfield 2016-07-22 14:36:50 +01:00 committed by Austin Burdine
parent 899ccaea38
commit 8d803d9862
5 changed files with 167 additions and 6 deletions

View File

@ -32,7 +32,11 @@ const TrimFocusInputComponent = GhostInput.extend({
},
sanitizeInput(input) {
return input.trim();
if (input && typeof input.trim === 'function') {
return input.trim();
} else {
return input;
}
},
_focus() {

View File

@ -155,6 +155,7 @@ export function testConfig() {
// this.urlPrefix = ''; // make this `http://localhost:8080`, for example, if your API is on a different server
this.namespace = 'ghost/api/v0.1'; // make this `api`, for example, if your API is namespaced
// this.timing = 400; // delay for each request, automatically set to 0 during testing
// this.logging = true;
/* Authentication ------------------------------------------------------- */

View File

@ -12,24 +12,26 @@
<input style="display:none;" type="password" name="fakepasswordremembered"/>
{{gh-profile-image fileStorage=config.fileStorage email=model.email setImage="setImage"}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="email"}}
{{#gh-form-group}}
<label for="email-address">Email address</label>
<span class="input-icon icon-mail">
{{gh-input model.email type="email" name="email" placeholder="Eg. john@example.com" onenter=(action "signup") disabled="disabled" autocorrect="off" focusOut=(action "validate" "email") update=(action (mut model.email))}}
{{gh-input model.email type="email" name="email" placeholder="Eg. john@example.com" disabled="disabled" autocorrect="off"}}
</span>
{{gh-error-message errors=model.errors property="email"}}
{{/gh-form-group}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="name"}}
<label for="full-name">Full name</label>
<span class="input-icon icon-user">
{{gh-trim-focus-input model.name tabindex="1" type="text" name="name" placeholder="Eg. John H. Watson" enter=(action "signup") autocorrect="off" focusOut=(action "validate" "name") update=(action (mut model.name))}}
{{gh-trim-focus-input model.name tabindex="1" type="text" name="name" placeholder="Eg. John H. Watson" onenter=(action "signup") autocorrect="off" focusOut=(action "validate" "name") update=(action (mut model.name))}}
</span>
{{gh-error-message errors=model.errors property="name"}}
{{/gh-form-group}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="password"}}
<label for="password">Password</label>
<span class="input-icon icon-lock">
{{gh-input model.password tabindex="2" type="password" name="password" enter=(action "signup") autocorrect="off" focusOut=(action "validate" "password") update=(action (mut model.password))}}
{{gh-input model.password tabindex="2" type="password" name="password" placeholder="At least 8 characters" onenter=(action "signup") autocorrect="off" focusOut=(action "validate" "password") update=(action (mut model.password))}}
</span>
{{gh-error-message errors=model.errors property="password"}}
{{/gh-form-group}}

View File

@ -0,0 +1,142 @@
/* jshint expr:true */
import {
describe,
it,
beforeEach,
afterEach
} from 'mocha';
import { expect } from 'chai';
import startApp from '../helpers/start-app';
import destroyApp from '../helpers/destroy-app';
import $ from 'jquery';
describe('Acceptance: Signup', function() {
let application;
beforeEach(function() {
application = startApp();
server.loadFixtures();
});
afterEach(function() {
destroyApp(application);
});
it('can signup successfully', function() {
// token details:
// "1470346017929|kevin+test2@ghost.org|2cDnQc3g7fQTj9nNK4iGPSGfvomkLdXf68FuWgS66Ug="
visit('/signup/MTQ3MDM0NjAxNzkyOXxrZXZpbit0ZXN0MkBnaG9zdC5vcmd8MmNEblFjM2c3ZlFUajluTks0aUdQU0dmdm9ta0xkWGY2OEZ1V2dTNjZVZz0');
andThen(function () {
expect(currentPath()).to.equal('signup');
// email address should be pre-filled and disabled
expect(
find('input[name="email"]').val(),
'email field value'
).to.equal('kevin+test2@ghost.org');
expect(
find('input[name="email"]').is(':disabled'),
'email field is disabled'
).to.be.true;
});
// focus out in Name field triggers inline error
triggerEvent('input[name="name"]', 'blur');
andThen(function () {
expect(
find('input[name="name"]').closest('.form-group').hasClass('error'),
'name field group has error class when empty'
).to.be.true;
expect(
find('input[name="name"]').closest('.form-group').find('.response').text().trim(),
'name inline-error text'
).to.match(/Please enter a name/);
});
// entering text in Name field clears error
fillIn('input[name="name"]', 'Test User');
triggerEvent('input[name="name"]', 'blur');
andThen(function () {
expect(
find('input[name="name"]').closest('.form-group').hasClass('error'),
'name field loses error class after text input'
).to.be.false;
expect(
find('input[name="name"]').closest('.form-group').find('.response').text().trim(),
'name field error is removed after text input'
).to.equal('');
});
// focus out in Name field triggers inline error
triggerEvent('input[name="password"]', 'blur');
andThen(function () {
expect(
find('input[name="password"]').closest('.form-group').hasClass('error'),
'password field group has error class when empty'
).to.be.true;
expect(
find('input[name="password"]').closest('.form-group').find('.response').text().trim(),
'password field error text'
).to.match(/must be at least 8 characters/);
});
// entering valid text in Password field clears error
fillIn('input[name="password"]', 'ValidPassword');
triggerEvent('input[name="password"]', 'blur');
andThen(function () {
expect(
find('input[name="password"]').closest('.form-group').hasClass('error'),
'password field loses error class after text input'
).to.be.false;
expect(
find('input[name="password"]').closest('.form-group').find('.response').text().trim(),
'password field error is removed after text input'
).to.equal('');
});
// submitting sends correct details and redirects to content screen
click('.btn-green');
server.get('/authentication/invitation', function (db, request) {
return {
invitation: [{valid: true}]
};
});
server.post('/authentication/invitation/', function (db, request) {
let params = $.deparam(request.requestBody);
expect(params.invitation[0].name).to.equal('Test User');
expect(params.invitation[0].email).to.equal('kevin+test2@ghost.org');
expect(params.invitation[0].password).to.equal('ValidPassword');
expect(params.invitation[0].token).to.equal('MTQ3MDM0NjAxNzkyOXxrZXZpbit0ZXN0MkBnaG9zdC5vcmd8MmNEblFjM2c3ZlFUajluTks0aUdQU0dmdm9ta0xkWGY2OEZ1V2dTNjZVZz0');
// ensure that `/users/me/` request returns a user
server.create('user', {email: 'kevin@test2@ghost.org'});
return {
invitation: [{
message: 'Invitation accepted.'
}]
};
});
andThen(function () {
expect(currentPath()).to.equal('posts.index');
});
});
it('redirects if already logged in');
it('redirects with alert on invalid token');
it('redirects with alert on non-existant or expired token');
});

View File

@ -35,5 +35,17 @@ describeComponent(
this.render(hbs`{{gh-trim-focus-input text shouldFocus=true}}`);
expect(this.$('.gh-input').attr('autofocus')).to.be.ok;
});
it('handles undefined values', function () {
this.set('text', undefined);
this.render(hbs`{{gh-trim-focus-input text shouldFocus=true}}`);
expect(this.$('.gh-input').attr('autofocus')).to.be.ok;
});
it('handles non-string values', function () {
this.set('text', 10);
this.render(hbs`{{gh-trim-focus-input text shouldFocus=true}}`);
expect(this.$('.gh-input').val()).to.equal('10');
});
}
);