From 8d803d986296dbd4ebe55f351dc68e765f8bbcf4 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Fri, 22 Jul 2016 14:36:50 +0100 Subject: [PATCH] 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 --- .../app/components/gh-trim-focus-input.js | 6 +- ghost/admin/app/mirage/config.js | 1 + ghost/admin/app/templates/signup.hbs | 12 +- ghost/admin/tests/acceptance/signup-test.js | 142 ++++++++++++++++++ .../components/gh-trim-focus-input-test.js | 12 ++ 5 files changed, 167 insertions(+), 6 deletions(-) create mode 100644 ghost/admin/tests/acceptance/signup-test.js diff --git a/ghost/admin/app/components/gh-trim-focus-input.js b/ghost/admin/app/components/gh-trim-focus-input.js index 7c19baa9aa..bf77f6d8d9 100644 --- a/ghost/admin/app/components/gh-trim-focus-input.js +++ b/ghost/admin/app/components/gh-trim-focus-input.js @@ -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() { diff --git a/ghost/admin/app/mirage/config.js b/ghost/admin/app/mirage/config.js index 4c20e6dc44..735836b128 100644 --- a/ghost/admin/app/mirage/config.js +++ b/ghost/admin/app/mirage/config.js @@ -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 ------------------------------------------------------- */ diff --git a/ghost/admin/app/templates/signup.hbs b/ghost/admin/app/templates/signup.hbs index e233daa838..3413acabdb 100644 --- a/ghost/admin/app/templates/signup.hbs +++ b/ghost/admin/app/templates/signup.hbs @@ -12,24 +12,26 @@ {{gh-profile-image fileStorage=config.fileStorage email=model.email setImage="setImage"}} - {{#gh-form-group errors=model.errors hasValidated=hasValidated property="email"}} + + {{#gh-form-group}} - {{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"}} - {{gh-error-message errors=model.errors property="email"}} {{/gh-form-group}} + {{#gh-form-group errors=model.errors hasValidated=hasValidated property="name"}} - {{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))}} {{gh-error-message errors=model.errors property="name"}} {{/gh-form-group}} + {{#gh-form-group errors=model.errors hasValidated=hasValidated property="password"}} - {{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))}} {{gh-error-message errors=model.errors property="password"}} {{/gh-form-group}} diff --git a/ghost/admin/tests/acceptance/signup-test.js b/ghost/admin/tests/acceptance/signup-test.js new file mode 100644 index 0000000000..5e15c52890 --- /dev/null +++ b/ghost/admin/tests/acceptance/signup-test.js @@ -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'); +}); diff --git a/ghost/admin/tests/integration/components/gh-trim-focus-input-test.js b/ghost/admin/tests/integration/components/gh-trim-focus-input-test.js index 1687a7af08..cae964bb02 100644 --- a/ghost/admin/tests/integration/components/gh-trim-focus-input-test.js +++ b/ghost/admin/tests/integration/components/gh-trim-focus-input-test.js @@ -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'); + }); } );