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');
+ });
}
);