From 2a30f919d9a515abd88951b64333668e297122a7 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 26 Oct 2015 11:48:38 +0000 Subject: [PATCH] Finish changes in #5807 (debounced gravatar load in gh-profile-image) refs #5807, #5797 - add configurable debounce period - rename `hasEmail` to `displayGravatar` to better reflect it's purpose - add tests --- .../client/app/components/gh-profile-image.js | 57 +++++++----- .../templates/components/gh-profile-image.hbs | 2 +- .../components/gh-profile-image-test.js | 91 +++++++++++++++++++ .../unit/components/gh-profile-image-test.js | 51 ----------- 4 files changed, 126 insertions(+), 75 deletions(-) create mode 100644 core/client/tests/integration/components/gh-profile-image-test.js delete mode 100644 core/client/tests/unit/components/gh-profile-image-test.js diff --git a/core/client/app/components/gh-profile-image.js b/core/client/app/components/gh-profile-image.js index 14f2383b73..9a53faa69f 100644 --- a/core/client/app/components/gh-profile-image.js +++ b/core/client/app/components/gh-profile-image.js @@ -5,46 +5,52 @@ import Ember from 'ember'; * A component to manage a user profile image. By default it just handles picture uploads, * but if passed a bound 'email' property it will render the user's gravatar image * - * Example: {{gh-profile-image email=controllerEmailProperty setImage="controllerActionName"}} + * Example: {{gh-profile-image email=controllerEmailProperty setImage="controllerActionName" debounce=500}} * - * @param {int} size The size of the image to render - * @param {String} email Reference to a bound email object if gravatar image behavior is desired. - * @param {String} setImage The string name of the action on the controller to be called when an image is added. - * @property {Boolean} hasUploadedImage Whether or not the user has uploaded an image (whether or not to show the default image/gravatar image) - * @property {String} defaultImage String containing the background-image css property of the default user profile image - * @property {String} imageBackground String containing the background-image css property with the gravatar url + * @param {int} size The size of the image to render + * @param {String} email Reference to a bound email object if gravatar image behavior is desired. + * @param {String|action} setImage The string name of the action on the controller to be called when an image is added. + * @param {int} debounce Period to wait after changes to email before attempting to load gravatar + * @property {Boolean} hasUploadedImage Whether or not the user has uploaded an image (whether or not to show the default image/gravatar image) + * @property {String} defaultImage String containing the background-image css property of the default user profile image + * @property {String} imageBackground String containing the background-image css property with the gravatar url */ export default Ember.Component.extend({ email: '', - validEmail: '', size: 90, + debounce: 300, + + validEmail: '', hasUploadedImage: false, fileStorage: true, ghostPaths: Ember.inject.service('ghost-paths'), - hasEmail: Ember.computed.notEmpty('email'), + displayGravatar: Ember.computed.notEmpty('validEmail'), defaultImage: Ember.computed('ghostPaths', function () { - var url = this.get('ghostPaths.url').asset('/shared/img/user-image.png'); - return `background-image: url(${url})`.htmlSafe(); + const url = this.get('ghostPaths.url').asset('/shared/img/user-image.png'); + return Ember.String.htmlSafe(`background-image: url(${url})`); }), trySetValidEmail: function () { - var email = this.get('email'); - this.set('validEmail', validator.isEmail(email) ? email : ''); + if (!this.get('isDestroyed')) { + const email = this.get('email'); + this.set('validEmail', validator.isEmail(email) ? email : ''); + } }, - didReceiveAttrs: function () { - Ember.run.debounce(this, 'trySetValidEmail', 500); + didReceiveAttrs: function (attrs) { + const timeout = parseInt(attrs.newAttrs.throttle || this.get('debounce')); + Ember.run.debounce(this, 'trySetValidEmail', timeout); }, imageBackground: Ember.computed('validEmail', 'size', function () { - var email = this.get('validEmail'), - size = this.get('size'), - url; + const email = this.get('validEmail'), + size = this.get('size'); + if (email) { - url = `http://www.gravatar.com/avatar/${md5(email)}?s=${size}&d=blank`; - return `background-image: url(${url})`.htmlSafe(); + let url = `http://www.gravatar.com/avatar/${md5(email)}?s=${size}&d=blank`; + return Ember.String.htmlSafe(`background-image: url(${url})`); } }), @@ -52,6 +58,9 @@ export default Ember.Component.extend({ var size = this.get('size'), uploadElement = this.$('.js-file-input'); + // Fire this immediately in case we're initialized with a valid email + this.trySetValidEmail(); + // while theoretically the 'add' and 'processalways' functions could be // added as properties of the hash passed to fileupload(), for some reason // they needed to be placed in an on() call for the add method to work correctly @@ -69,11 +78,13 @@ export default Ember.Component.extend({ }, willDestroyElement: function () { - this.$('.js-file-input').fileupload('destroy'); + if (this.$('.js-file-input').data()['blueimp-fileupload']) { + this.$('.js-file-input').fileupload('destroy'); + } }, queueFile: function (e, data) { - var fileName = data.files[0].name; + const fileName = data.files[0].name; if ((/\.(gif|jpe?g|png|svg?z)$/i).test(fileName)) { this.sendAction('setImage', data); @@ -81,7 +92,7 @@ export default Ember.Component.extend({ }, triggerPreview: function (e, data) { - var file = data.files[data.index]; + const file = data.files[data.index]; if (file.preview) { this.set('hasUploadedImage', true); // necessary jQuery code because file.preview is a raw DOM object diff --git a/core/client/app/templates/components/gh-profile-image.hbs b/core/client/app/templates/components/gh-profile-image.hbs index bf8c1889ba..876767bab6 100644 --- a/core/client/app/templates/components/gh-profile-image.hbs +++ b/core/client/app/templates/components/gh-profile-image.hbs @@ -2,7 +2,7 @@ {{#unless hasUploadedImage}}
- {{#if hasEmail}} + {{#if displayGravatar}}
User image
diff --git a/core/client/tests/integration/components/gh-profile-image-test.js b/core/client/tests/integration/components/gh-profile-image-test.js new file mode 100644 index 0000000000..fa335961a4 --- /dev/null +++ b/core/client/tests/integration/components/gh-profile-image-test.js @@ -0,0 +1,91 @@ +/* jshint expr:true */ +/* global md5 */ +import { expect } from 'chai'; +import { + describeComponent, + it +} from 'ember-mocha'; +import hbs from 'htmlbars-inline-precompile'; +import Ember from 'ember'; + +const {run} = Ember, + pathsStub = Ember.Service.extend({ + url: { + api: function () { + return ''; + }, + asset: function (src) { + return src; + } + } + }); + +describeComponent( + 'gh-profile-image', + 'Integration: Component: gh-profile-image', + { + integration: true + }, + function () { + beforeEach(function () { + this.register('service:ghost-paths', pathsStub); + this.inject.service('ghost-paths', {as: 'ghost-paths'}); + }); + + it('renders', function () { + this.set('email', ''); + + this.render(hbs` + {{gh-profile-image email=email}} + `); + + expect(this.$()).to.have.length(1); + }); + + it('immediately renders the gravatar if valid email supplied', function () { + let email = 'test@example.com', + expectedUrl = `http://www.gravatar.com/avatar/${md5(email)}?s=100&d=blank`; + + this.set('email', email); + + this.render(hbs` + {{gh-profile-image email=email size=100 debounce=300}} + `); + + expect(this.$('.gravatar-img').attr('style'), 'gravatar image style') + .to.equal(`background-image: url(${expectedUrl})`); + }); + + it('throttles gravatar loading as email is changed', function (done) { + let email = 'test@example.com', + expectedUrl = `http://www.gravatar.com/avatar/${md5(email)}?s=100&d=blank`; + + this.set('email', 'test'); + + this.render(hbs` + {{gh-profile-image email=email size=100 debounce=300}} + `); + + expect(this.$('.gravatar-img').length, '.gravatar-img not shown for invalid email') + .to.equal(0); + + run(() => { + this.set('email', email); + }); + + expect(this.$('.gravatar-img').length, '.gravatar-img not immediately changed on email change') + .to.equal(0); + + Ember.run.later(this, function () { + expect(this.$('.gravatar-img').length, '.gravatar-img still not shown before throttle timeout') + .to.equal(0); + }, 250); + + Ember.run.later(this, function () { + expect(this.$('.gravatar-img').attr('style'), '.gravatar-img style after timeout') + .to.equal(`background-image: url(${expectedUrl})`); + done(); + }, 400); + }); + } +); diff --git a/core/client/tests/unit/components/gh-profile-image-test.js b/core/client/tests/unit/components/gh-profile-image-test.js deleted file mode 100644 index 689ba4df75..0000000000 --- a/core/client/tests/unit/components/gh-profile-image-test.js +++ /dev/null @@ -1,51 +0,0 @@ -/* jshint expr:true */ -/* global md5 */ -import { expect } from 'chai'; -import { - describeComponent, - it -} from 'ember-mocha'; - -describeComponent( - 'gh-profile-image', - 'Unit: Component: gh-profile-image', - { - unit: true, - needs: ['service:ghost-paths'] - }, - 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'); - }); - it('renders the gravatar image background if email is supplied', function () { - var component = this.subject(), - testEmail = 'test@example.com', - style, size; - - Ember.run(function () { - component.set('email', testEmail); - }); - - this.render(); - - size = component.get('size'); - - style = 'url(http://www.gravatar.com/avatar/' + md5(testEmail) + '?s=' + size + '&d=blank)'; - - expect(component.$('#account-image').css('background-image')).to.equal(style); - }); - it('doesn\'t render the gravatar image background if email isn\'t supplied', function () { - var component = this.subject(); - - this.render(); - - expect(component.$('#account-image').length).to.equal(0); - }); - } -);