🔥 remove URL input option from image upload components

refs https://github.com/TryGhost/Ghost/issues/8032
- `fileStorage: false` config is going away, it predates storage engines and will simplify future image optimisation work
- simplifies UI, it can be brought back in the future in a more robust fashion if required
This commit is contained in:
Kevin Ansfield 2017-03-03 08:59:01 +00:00 committed by Austin Burdine
parent 0366bde545
commit ea58dc6f85
18 changed files with 318 additions and 640 deletions

View File

@ -9,12 +9,6 @@ export default Component.extend({
} }
}, },
onInput() {
if (typeof this.attrs.onInput === 'function') {
this.attrs.onInput(...arguments);
}
},
uploadStarted() { uploadStarted() {
if (typeof this.attrs.uploadStarted === 'function') { if (typeof this.attrs.uploadStarted === 'function') {
this.attrs.uploadStarted(...arguments); this.attrs.uploadStarted(...arguments);
@ -27,12 +21,6 @@ export default Component.extend({
} }
}, },
formChanged() {
if (typeof this.attrs.formChanged === 'function') {
this.attrs.formChanged(...arguments);
}
},
remove() { remove() {
invokeAction(this, 'remove'); invokeAction(this, 'remove');
} }

View File

@ -5,7 +5,6 @@ import {htmlSafe} from 'ember-string';
import {isBlank} from 'ember-utils'; import {isBlank} from 'ember-utils';
import {isEmberArray} from 'ember-array/utils'; import {isEmberArray} from 'ember-array/utils';
import run from 'ember-runloop'; import run from 'ember-runloop';
import {invokeAction} from 'ember-invoke-action'; import {invokeAction} from 'ember-invoke-action';
import ghostPaths from 'ghost-admin/utils/ghost-paths'; import ghostPaths from 'ghost-admin/utils/ghost-paths';
import { import {
@ -27,18 +26,15 @@ export default Component.extend({
accept: null, accept: null,
extensions: null, extensions: null,
uploadUrl: null, uploadUrl: null,
allowUrlInput: true,
validate: null, validate: null,
dragClass: null, dragClass: null,
failureMessage: null, failureMessage: null,
file: null, file: null,
formType: 'upload',
url: null, url: null,
uploadPercentage: 0, uploadPercentage: 0,
ajax: injectService(), ajax: injectService(),
config: injectService(),
notifications: injectService(), notifications: injectService(),
_defaultAccept: 'image/gif,image/jpg,image/jpeg,image/png,image/svg+xml', _defaultAccept: 'image/gif,image/jpg,image/jpeg,image/png,image/svg+xml',
@ -75,17 +71,6 @@ export default Component.extend({
return htmlSafe(`width: ${width}`); return htmlSafe(`width: ${width}`);
}), }),
canShowUploadForm: computed('config.fileStorage', function () {
return this.get('config.fileStorage') !== false;
}),
showUploadForm: computed('formType', function () {
let canShowUploadForm = this.get('canShowUploadForm');
let formType = this.get('formType');
return formType === 'upload' && canShowUploadForm;
}),
didReceiveAttrs() { didReceiveAttrs() {
let image = this.get('image'); let image = this.get('image');
this.set('url', image); this.set('url', image);
@ -102,8 +87,6 @@ export default Component.extend({
}, },
dragOver(event) { dragOver(event) {
let showUploadForm = this.get('showUploadForm');
if (!event.dataTransfer) { if (!event.dataTransfer) {
return; return;
} }
@ -116,32 +99,21 @@ export default Component.extend({
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
if (showUploadForm) { this.set('dragClass', '-drag-over');
this.set('dragClass', '-drag-over');
}
}, },
dragLeave(event) { dragLeave(event) {
let showUploadForm = this.get('showUploadForm');
event.preventDefault(); event.preventDefault();
this.set('dragClass', null);
if (showUploadForm) {
this.set('dragClass', null);
}
}, },
drop(event) { drop(event) {
let showUploadForm = this.get('showUploadForm');
event.preventDefault(); event.preventDefault();
this.set('dragClass', null); this.set('dragClass', null);
if (showUploadForm) { if (event.dataTransfer.files) {
if (event.dataTransfer.files) { this.send('fileSelected', event.dataTransfer.files);
this.send('fileSelected', event.dataTransfer.files);
}
} }
}, },
@ -269,24 +241,11 @@ export default Component.extend({
} }
}, },
onInput(url) {
this.set('url', url);
invokeAction(this, 'onInput', url);
},
reset() { reset() {
this.set('file', null); this.set('file', null);
this.set('uploadPercentage', 0); this.set('uploadPercentage', 0);
}, },
switchForm(formType) {
this.set('formType', formType);
run.scheduleOnce('afterRender', this, function () {
invokeAction(this, 'formChanged', formType);
});
},
saveUrl() { saveUrl() {
let url = this.get('url'); let url = this.get('url');
invokeAction(this, 'update', url); invokeAction(this, 'update', url);

View File

@ -29,7 +29,6 @@ export default Component.extend({
validEmail: '', validEmail: '',
hasUploadedImage: false, hasUploadedImage: false,
fileStorage: true,
ajax: AjaxService.create(), ajax: AjaxService.create(),
config: injectService(), config: injectService(),

View File

@ -73,51 +73,6 @@
font-size: 1.6rem; font-size: 1.6rem;
} }
.gh-image-uploader .image-upload,
.gh-image-uploader .image-url {
position: absolute;
bottom: 0;
left: 0;
display: block;
padding: 10px;
color: var(--midgrey);
text-decoration: none;
font-size: 14px;
line-height: 12px;
}
.gh-image-uploader a {
color: var(--midgrey);
text-decoration: none;
}
.gh-image-uploader a:hover {
color: var(--darkgrey);
}
.gh-image-uploader .image-upload:hover,
.gh-image-uploader .image-url:hover {
cursor: pointer;
}
.gh-image-uploader form {
padding: 55px 60px;
width: 100%;
}
.gh-image-uploader input.url {
margin: 0 0 10px 0;
padding: 9px 7px;
outline: 0;
background: #fff;
vertical-align: middle;
font: -webkit-small-control;
font-size: 1.4rem;
}
.gh-image-uploader input.url + .gh-btn.gh-btn-blue {
color: #fff;
}
.gh-image-uploader .image-cancel:hover { .gh-image-uploader .image-cancel:hover {
background: var(--red); background: var(--red);
color: #fff; color: #fff;

View File

@ -8,7 +8,6 @@
update=(action "updateImageSrc" uploader.index) update=(action "updateImageSrc" uploader.index)
remove=(action "updateImageSrc" uploader.index "") remove=(action "updateImageSrc" uploader.index "")
uploadStarted=uploadStarted uploadStarted=uploadStarted
uploadFinished=uploadFinished uploadFinished=uploadFinished}}
formChanged=(action "updateHeight")}}
{{/ember-wormhole}} {{/ember-wormhole}}
{{/each}} {{/each}}

View File

@ -10,9 +10,7 @@
text=text text=text
altText=altText altText=altText
update=(action 'update') update=(action 'update')
onInput=(action 'onInput')
uploadStarted=(action 'uploadStarted') uploadStarted=(action 'uploadStarted')
uploadFinished=(action 'uploadFinished') uploadFinished=(action 'uploadFinished')
formChanged=(action 'formChanged')
}} }}
{{/if}} {{/if}}

View File

@ -12,33 +12,10 @@
<button class="gh-btn gh-btn-green" {{action "reset"}}><span>Try Again</span></button> <button class="gh-btn gh-btn-green" {{action "reset"}}><span>Try Again</span></button>
{{/if}} {{/if}}
{{else}} {{else}}
{{#if showUploadForm}} {{!-- file selection/drag-n-drop --}}
{{!-- file selection/drag-n-drop --}} <div class="upload-form">
<div class="upload-form"> {{#gh-file-input multiple=false alt=description action=(action "fileSelected") accept=accept}}
{{#gh-file-input multiple=false alt=description action=(action "fileSelected") accept=accept}} <div class="description">{{description}}</div>
<div class="description">{{description}}</div> {{/gh-file-input}}
{{/gh-file-input}} </div>
</div>
{{#if allowUrlInput}}
<a class="image-url" {{action "switchForm" "url-input"}}>
<i class="icon-link"><span class="hidden">URL</span></i>
</a>
{{/if}}
{{else}}
{{!-- URL input --}}
<form class="url-form">
{{gh-input url class="url" placeholder="http://" update=(action "onInput") onenter=(action "saveUrl")}}
{{#if saveButton}}
<button class="gh-btn gh-btn-blue gh-input" {{action "saveUrl"}}><span>Save</span></button>
{{else}}
<div class="description">{{description}}</div>
{{/if}}
</form>
{{#if canShowUploadForm}}
<a class="image-upload icon-photos" title="Add image" {{action "switchForm" "upload"}}>
<span class="hidden">Upload</span>
</a>
{{/if}}
{{/if}}
{{/if}} {{/if}}

View File

@ -13,5 +13,5 @@
<span class="sr-only">Upload an image</span> <span class="sr-only">Upload an image</span>
</i> </i>
</span> </span>
{{#if fileStorage}}<input type="file" class="file-uploader js-file-input" name="uploadimage">{{/if}} <input type="file" class="file-uploader js-file-input" name="uploadimage">
</figure> </figure>

View File

@ -11,10 +11,8 @@
image=newUrl image=newUrl
saveButton=false saveButton=false
update=(action 'fileUploaded') update=(action 'fileUploaded')
onInput=(action (mut newUrl))
accept=model.accept accept=model.accept
extensions=model.extensions extensions=model.extensions
allowUrlInput=model.allowUrlInput
uploadUrl=model.uploadUrl uploadUrl=model.uploadUrl
}} }}
{{/if}} {{/if}}

View File

@ -66,7 +66,7 @@
{{#if showUploadIconModal}} {{#if showUploadIconModal}}
{{gh-fullscreen-modal "upload-image" {{gh-fullscreen-modal "upload-image"
model=(hash model=model imageProperty="icon" accept=iconMimeTypes extensions=iconExtensions allowUrlInput=false uploadUrl="/uploads/icon/") model=(hash model=model imageProperty="icon" accept=iconMimeTypes extensions=iconExtensions uploadUrl="/uploads/icon/")
close=(action "toggleUploadIconModal") close=(action "toggleUploadIconModal")
modifier="action wide"}} modifier="action wide"}}
{{/if}} {{/if}}
@ -86,7 +86,7 @@
{{#if showUploadLogoModal}} {{#if showUploadLogoModal}}
{{gh-fullscreen-modal "upload-image" {{gh-fullscreen-modal "upload-image"
model=(hash model=model imageProperty="logo" allowUrlInput=true) model=(hash model=model imageProperty="logo")
close=(action "toggleUploadLogoModal") close=(action "toggleUploadLogoModal")
modifier="action wide"}} modifier="action wide"}}
{{/if}} {{/if}}
@ -106,7 +106,7 @@
{{#if showUploadCoverModal}} {{#if showUploadCoverModal}}
{{gh-fullscreen-modal "upload-image" {{gh-fullscreen-modal "upload-image"
model=(hash model=model imageProperty="cover" allowUrlInput=true) model=(hash model=model imageProperty="cover")
close=(action "toggleUploadCoverModal") close=(action "toggleUploadCoverModal")
modifier="action wide"}} modifier="action wide"}}
{{/if}} {{/if}}

View File

@ -38,7 +38,7 @@
<input style="display:none;" type="text" name="fakeusernameremembered"/> <input style="display:none;" type="text" name="fakeusernameremembered"/>
<input style="display:none;" type="password" name="fakepasswordremembered"/> <input style="display:none;" type="password" name="fakepasswordremembered"/>
{{gh-profile-image fileStorage=config.fileStorage email=email setImage="setImage"}} {{gh-profile-image email=email setImage="setImage"}}
{{#gh-form-group errors=errors hasValidated=hasValidated property="email"}} {{#gh-form-group errors=errors hasValidated=hasValidated property="email"}}
<label for="email-address">Email address</label> <label for="email-address">Email address</label>
<span class="input-icon icon-mail"> <span class="input-icon icon-mail">

View File

@ -26,7 +26,7 @@
<input style="display:none;" type="text" name="fakeusernameremembered"/> <input style="display:none;" type="text" name="fakeusernameremembered"/>
<input style="display:none;" type="password" name="fakepasswordremembered"/> <input style="display:none;" type="password" name="fakepasswordremembered"/>
{{gh-profile-image fileStorage=config.fileStorage email=model.email setImage="setImage"}} {{gh-profile-image email=model.email setImage="setImage"}}
{{#gh-form-group}} {{#gh-form-group}}
<label for="email-address">Email address</label> <label for="email-address">Email address</label>

View File

@ -55,7 +55,7 @@
<button class="gh-btn gh-btn-default user-cover-edit" {{action "toggleUploadCoverModal"}}><span>Change Cover</span></button> <button class="gh-btn gh-btn-default user-cover-edit" {{action "toggleUploadCoverModal"}}><span>Change Cover</span></button>
{{#if showUploadCoverModal}} {{#if showUploadCoverModal}}
{{gh-fullscreen-modal "upload-image" {{gh-fullscreen-modal "upload-image"
model=(hash model=user imageProperty="cover" allowUrlInput=true) model=(hash model=user imageProperty="cover")
close=(action "toggleUploadCoverModal") close=(action "toggleUploadCoverModal")
modifier="action wide"}} modifier="action wide"}}
{{/if}} {{/if}}
@ -74,7 +74,7 @@
<button type="button" {{action "toggleUploadImageModal"}} class="edit-user-image">Edit Picture</button> <button type="button" {{action "toggleUploadImageModal"}} class="edit-user-image">Edit Picture</button>
{{#if showUploadImageModal}} {{#if showUploadImageModal}}
{{gh-fullscreen-modal "upload-image" {{gh-fullscreen-modal "upload-image"
model=(hash model=user imageProperty="image" allowUrlInput=true) model=(hash model=user imageProperty="image")
close=(action "toggleUploadImageModal") close=(action "toggleUploadImageModal")
modifier="action wide"}} modifier="action wide"}}
{{/if}} {{/if}}

View File

@ -33,12 +33,10 @@ export default Component.extend({
dragClass: null, dragClass: null,
failureMessage: null, failureMessage: null,
file: null, file: null,
formType: 'upload',
url: null, url: null,
uploadPercentage: 0, uploadPercentage: 0,
ajax: injectService(), ajax: injectService(),
config: injectService(),
notifications: injectService(), notifications: injectService(),
// TODO: this wouldn't be necessary if the server could accept direct // TODO: this wouldn't be necessary if the server could accept direct
@ -70,17 +68,6 @@ export default Component.extend({
return htmlSafe(`width: ${width}`); return htmlSafe(`width: ${width}`);
}), }),
canShowUploadForm: computed('config.fileStorage', function () {
return this.get('config.fileStorage') !== false;
}),
showUploadForm: computed('formType', function () {
let canShowUploadForm = this.get('canShowUploadForm');
let formType = this.get('formType');
return formType === 'upload' && canShowUploadForm;
}),
didReceiveAttrs() { didReceiveAttrs() {
let image = this.get('payload'); let image = this.get('payload');
if (image.img) { if (image.img) {
@ -93,8 +80,6 @@ export default Component.extend({
}, },
dragOver(event) { dragOver(event) {
let showUploadForm = this.get('showUploadForm');
if (!event.dataTransfer) { if (!event.dataTransfer) {
return; return;
} }
@ -107,32 +92,21 @@ export default Component.extend({
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
if (showUploadForm) { this.set('dragClass', '-drag-over');
this.set('dragClass', '-drag-over');
}
}, },
dragLeave(event) { dragLeave(event) {
let showUploadForm = this.get('showUploadForm');
event.preventDefault(); event.preventDefault();
this.set('dragClass', null);
if (showUploadForm) {
this.set('dragClass', null);
}
}, },
drop(event) { drop(event) {
let showUploadForm = this.get('showUploadForm');
event.preventDefault(); event.preventDefault();
this.set('dragClass', null); this.set('dragClass', null);
if (showUploadForm) { if (event.dataTransfer.files) {
if (event.dataTransfer.files) { this.send('fileSelected', event.dataTransfer.files);
this.send('fileSelected', event.dataTransfer.files);
}
} }
}, },
@ -272,24 +246,11 @@ export default Component.extend({
} }
}, },
onInput(url) {
this.set('url', url);
invokeAction(this, 'onInput', url);
},
reset() { reset() {
this.set('file', null); this.set('file', null);
this.set('uploadPercentage', 0); this.set('uploadPercentage', 0);
}, },
switchForm(formType) {
this.set('formType', formType);
run.scheduleOnce('afterRender', this, function () {
invokeAction(this, 'formChanged', formType);
});
},
saveUrl() { saveUrl() {
let url = this.get('url'); let url = this.get('url');
invokeAction(this, 'update', url); invokeAction(this, 'update', url);

View File

@ -15,32 +15,10 @@
<button class="btn btn-green" {{action "reset"}}>Try Again</button> <button class="btn btn-green" {{action "reset"}}>Try Again</button>
{{/if}} {{/if}}
{{else}} {{else}}
{{#if showUploadForm}}
{{!-- file selection/drag-n-drop --}} {{!-- file selection/drag-n-drop --}}
<div class="upload-form"> <div class="upload-form">
{{#gh-file-input multiple=false alt=description action=(action 'fileSelected') accept=accept}} {{#gh-file-input multiple=false alt=description action=(action 'fileSelected') accept=accept}}
<div class="description">{{description}}</div> <div class="description">{{description}}</div>
{{/gh-file-input}} {{/gh-file-input}}
</div> </div>
<a class="image-url" {{action 'switchForm' 'url-input'}}>
<i class="icon-link"><span class="hidden">URL</span></i>
</a>
{{else}}
{{!-- URL input --}}
<form class="url-form">
{{gh-input url class="url" placeholder="http://" update=(action "onInput") onenter=(action "saveUrl")}}
{{#if saveButton}}
<button class="btn btn-blue gh-input" {{action 'saveUrl'}}>Save</button>
{{else}}
<div class="description">{{description}}</div>
{{/if}}
</form>
{{#if canShowUploadForm}}
<a class="image-upload icon-photos" title="Add image" {{action 'switchForm' 'upload'}}>
<span class="hidden">Upload</span>
</a>
{{/if}}
{{/if}}
{{/if}} {{/if}}

View File

@ -101,7 +101,6 @@ describe('Acceptance: Settings - General', function () {
andThen(() => { andThen(() => {
expect(find('.fullscreen-modal .modal-content .gh-image-uploader .description').text()).to.equal('Upload an image'); expect(find('.fullscreen-modal .modal-content .gh-image-uploader .description').text()).to.equal('Upload an image');
expect(find('.fullscreen-modal .modal-content .gh-image-uploader .image-url').length, 'url upload').to.equal(1);
}); });
// click cancel button // click cancel button
@ -121,7 +120,6 @@ describe('Acceptance: Settings - General', function () {
andThen(() => { andThen(() => {
expect(find('.fullscreen-modal .modal-content .gh-image-uploader .description').text()).to.equal('Upload an image'); expect(find('.fullscreen-modal .modal-content .gh-image-uploader .description').text()).to.equal('Upload an image');
expect(find('.fullscreen-modal .modal-content .gh-image-uploader .image-url').length, 'url upload').to.equal(0);
}); });
// click cancel button // click cancel button

View File

@ -12,14 +12,6 @@ import run from 'ember-runloop';
import Service from 'ember-service'; import Service from 'ember-service';
import {UnsupportedMediaTypeError} from 'ghost-admin/services/ajax'; import {UnsupportedMediaTypeError} from 'ghost-admin/services/ajax';
const keyCodes = {
enter: 13
};
const configStub = Service.extend({
fileStorage: true
});
const notificationsStub = Service.extend({ const notificationsStub = Service.extend({
showAPIError(/* error, options */) { showAPIError(/* error, options */) {
// noop - to be stubbed // noop - to be stubbed
@ -60,10 +52,8 @@ describe('Integration: Component: gh-image-uploader', function() {
let server; let server;
beforeEach(function () { beforeEach(function () {
this.register('service:config', configStub);
this.register('service:session', sessionStub); this.register('service:session', sessionStub);
this.register('service:notifications', notificationsStub); this.register('service:notifications', notificationsStub);
this.inject.service('config', {as: 'configService'});
this.inject.service('session', {as: 'sessionService'}); this.inject.service('session', {as: 'sessionService'});
this.inject.service('notifications', {as: 'notifications'}); this.inject.service('notifications', {as: 'notifications'});
this.set('update', function () {}); this.set('update', function () {});
@ -80,498 +70,387 @@ describe('Integration: Component: gh-image-uploader', function() {
expect(this.$()).to.have.length(1); expect(this.$()).to.have.length(1);
}); });
it('defaults to upload form', function () { it('renders form with supplied alt text', function () {
this.render(hbs`{{gh-image-uploader image=image}}`); this.render(hbs`{{gh-image-uploader image=image altText="text test"}}`);
expect(this.$('input[type="file"]').length).to.equal(1); expect(this.$('.description').text().trim()).to.equal('Upload image of "text test"');
}); });
it('defaults to url form with no filestorage config', function () { it('renders form with supplied text', function () {
this.set('configService.fileStorage', false); this.render(hbs`{{gh-image-uploader image=image text="text test"}}`);
this.render(hbs`{{gh-image-uploader image=image}}`); expect(this.$('.description').text().trim()).to.equal('text test');
expect(this.$('input[type="file"]').length).to.equal(0);
expect(this.$('input[type="text"].url').length).to.equal(1);
}); });
it('can switch between form types', function () { it('generates request to correct endpoint', function (done) {
this.render(hbs`{{gh-image-uploader image=image}}`); stubSuccessfulUpload(server);
expect(this.$('input[type="file"]').length).to.equal(1);
expect(this.$('input[type="text"].url').length).to.equal(0);
this.$('a.image-url').click(); this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
expect(this.$('input[type="file"]').length, 'upload form is visible after switch to url form') wait().then(() => {
.to.equal(0); expect(server.handledRequests.length).to.equal(1);
expect(this.$('input[type="text"].url').length, 'url form is visible after switch to url form') expect(server.handledRequests[0].url).to.equal('/ghost/api/v0.1/uploads/');
.to.equal(1); expect(server.handledRequests[0].requestHeaders.Authorization).to.be.undefined;
done();
this.$('a.image-upload').click(); });
expect(this.$('input[type="file"]').length, 'upload form is visible after switch to upload form')
.to.equal(1);
expect(this.$('input[type="text"].url').length, 'url form is visible after switch to upload form')
.to.equal(0);
}); });
it('triggers formChanged action when switching between forms', function () { it('adds authentication headers to request', function (done) {
let formChanged = sinon.spy(); stubSuccessfulUpload(server);
this.set('formChanged', formChanged);
this.render(hbs`{{gh-image-uploader image=image formChanged=(action formChanged)}}`); this.get('sessionService').set('isAuthenticated', true);
this.$('a.image-url').click(); this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
this.$('a.image-upload').click(); fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
expect(formChanged.calledTwice).to.be.true; wait().then(() => {
expect(formChanged.firstCall.args[0]).to.equal('url-input'); let [request] = server.handledRequests;
expect(formChanged.secondCall.args[0]).to.equal('upload'); expect(request.requestHeaders.Authorization).to.equal('Bearer token');
done();
});
}); });
describe('file upload form', function () { it('fires update action on successful upload', function (done) {
it('renders form with supplied alt text', function () { let update = sinon.spy();
this.render(hbs`{{gh-image-uploader image=image altText="text test"}}`); this.set('update', update);
expect(this.$('.description').text().trim()).to.equal('Upload image of "text test"');
stubSuccessfulUpload(server);
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(update.calledOnce).to.be.true;
expect(update.firstCall.args[0]).to.equal('/content/images/test.png');
done();
}); });
});
it('renders form with supplied text', function () { it('doesn\'t fire update action on failed upload', function (done) {
this.render(hbs`{{gh-image-uploader image=image text="text test"}}`); let update = sinon.spy();
expect(this.$('.description').text().trim()).to.equal('text test'); this.set('update', update);
stubFailedUpload(server, 500);
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(update.calledOnce).to.be.false;
done();
}); });
});
it('generates request to correct endpoint', function (done) { it('fires fileSelected action on file selection', function (done) {
stubSuccessfulUpload(server); let fileSelected = sinon.spy();
this.set('fileSelected', fileSelected);
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`); stubSuccessfulUpload(server);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => { this.render(hbs`{{gh-image-uploader image=image fileSelected=(action fileSelected) update=(action update)}}`);
expect(server.handledRequests.length).to.equal(1); fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
expect(server.handledRequests[0].url).to.equal('/ghost/api/v0.1/uploads/');
expect(server.handledRequests[0].requestHeaders.Authorization).to.be.undefined; wait().then(() => {
done(); expect(fileSelected.calledOnce).to.be.true;
}); expect(fileSelected.args[0]).to.not.be.blank;
done();
}); });
});
it('adds authentication headers to request', function (done) { it('fires uploadStarted action on upload start', function (done) {
stubSuccessfulUpload(server); let uploadStarted = sinon.spy();
this.set('uploadStarted', uploadStarted);
this.get('sessionService').set('isAuthenticated', true); stubSuccessfulUpload(server);
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`); this.render(hbs`{{gh-image-uploader image=image uploadStarted=(action uploadStarted) update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'}); fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => { wait().then(() => {
let [request] = server.handledRequests; expect(uploadStarted.calledOnce).to.be.true;
expect(request.requestHeaders.Authorization).to.equal('Bearer token'); done();
done();
});
}); });
});
it('fires update action on successful upload', function (done) { it('fires uploadFinished action on successful upload', function (done) {
let update = sinon.spy(); let uploadFinished = sinon.spy();
this.set('update', update); this.set('uploadFinished', uploadFinished);
stubSuccessfulUpload(server); stubSuccessfulUpload(server);
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`); this.render(hbs`{{gh-image-uploader image=image uploadFinished=(action uploadFinished) update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'}); fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => { wait().then(() => {
expect(update.calledOnce).to.be.true; expect(uploadFinished.calledOnce).to.be.true;
expect(update.firstCall.args[0]).to.equal('/content/images/test.png'); done();
done();
});
}); });
});
it('doesn\'t fire update action on failed upload', function (done) { it('fires uploadFinished action on failed upload', function (done) {
let update = sinon.spy(); let uploadFinished = sinon.spy();
this.set('update', update); this.set('uploadFinished', uploadFinished);
stubFailedUpload(server, 500); stubFailedUpload(server);
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`); this.render(hbs`{{gh-image-uploader image=image uploadFinished=(action uploadFinished) update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'}); fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => { wait().then(() => {
expect(update.calledOnce).to.be.false; expect(uploadFinished.calledOnce).to.be.true;
done(); done();
});
}); });
});
it('fires fileSelected action on file selection', function (done) { it('displays invalid file type error', function (done) {
let fileSelected = sinon.spy(); stubFailedUpload(server, 415, 'UnsupportedMediaTypeError');
this.set('fileSelected', fileSelected); this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
stubSuccessfulUpload(server); wait().then(() => {
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
this.render(hbs`{{gh-image-uploader image=image fileSelected=(action fileSelected) update=(action update)}}`); expect(this.$('.failed').text()).to.match(/The image type you uploaded is not supported/);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'}); expect(this.$('.gh-btn-green').length, 'reset button is displayed').to.equal(1);
expect(this.$('.gh-btn-green').text()).to.equal('Try Again');
wait().then(() => { done();
expect(fileSelected.calledOnce).to.be.true;
expect(fileSelected.args[0]).to.not.be.blank;
done();
});
}); });
});
it('fires uploadStarted action on upload start', function (done) { it('displays file too large for server error', function (done) {
let uploadStarted = sinon.spy(); stubFailedUpload(server, 413, 'RequestEntityTooLargeError');
this.set('uploadStarted', uploadStarted); this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
stubSuccessfulUpload(server); wait().then(() => {
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
this.render(hbs`{{gh-image-uploader image=image uploadStarted=(action uploadStarted) update=(action update)}}`); expect(this.$('.failed').text()).to.match(/The image you uploaded was larger/);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'}); done();
wait().then(() => {
expect(uploadStarted.calledOnce).to.be.true;
done();
});
}); });
});
it('fires uploadFinished action on successful upload', function (done) { it('handles file too large error directly from the web server', function (done) {
let uploadFinished = sinon.spy(); server.post('/ghost/api/v0.1/uploads/', function () {
this.set('uploadFinished', uploadFinished); return [413, {}, ''];
stubSuccessfulUpload(server);
this.render(hbs`{{gh-image-uploader image=image uploadFinished=(action uploadFinished) update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(uploadFinished.calledOnce).to.be.true;
done();
});
}); });
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
it('fires uploadFinished action on failed upload', function (done) { wait().then(() => {
let uploadFinished = sinon.spy(); expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
this.set('uploadFinished', uploadFinished); expect(this.$('.failed').text()).to.match(/The image you uploaded was larger/);
done();
stubFailedUpload(server);
this.render(hbs`{{gh-image-uploader image=image uploadFinished=(action uploadFinished) update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(uploadFinished.calledOnce).to.be.true;
done();
});
}); });
});
it('displays invalid file type error', function (done) { it('displays other server-side error with message', function (done) {
stubFailedUpload(server, 415, 'UnsupportedMediaTypeError'); stubFailedUpload(server, 400, 'UnknownError');
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`); this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'}); fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => { wait().then(() => {
expect(this.$('.failed').length, 'error message is displayed').to.equal(1); expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
expect(this.$('.failed').text()).to.match(/The image type you uploaded is not supported/); expect(this.$('.failed').text()).to.match(/Error: UnknownError/);
expect(this.$('.gh-btn-green').length, 'reset button is displayed').to.equal(1); done();
expect(this.$('.gh-btn-green').text()).to.equal('Try Again');
done();
});
}); });
});
it('displays file too large for server error', function (done) { it('handles unknown failure', function (done) {
stubFailedUpload(server, 413, 'RequestEntityTooLargeError'); server.post('/ghost/api/v0.1/uploads/', function () {
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`); return [500, {'Content-Type': 'application/json'}, ''];
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
expect(this.$('.failed').text()).to.match(/The image you uploaded was larger/);
done();
});
}); });
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
it('handles file too large error directly from the web server', function (done) { wait().then(() => {
server.post('/ghost/api/v0.1/uploads/', function () { expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
return [413, {}, '']; expect(this.$('.failed').text()).to.match(/Something went wrong/);
}); done();
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
expect(this.$('.failed').text()).to.match(/The image you uploaded was larger/);
done();
});
}); });
});
it('displays other server-side error with message', function (done) { it('triggers notifications.showAPIError for VersionMismatchError', function (done) {
stubFailedUpload(server, 400, 'UnknownError'); let showAPIError = sinon.spy();
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`); this.set('notifications.showAPIError', showAPIError);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => { stubFailedUpload(server, 400, 'VersionMismatchError');
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
expect(this.$('.failed').text()).to.match(/Error: UnknownError/); this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
done(); fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
});
wait().then(() => {
expect(showAPIError.calledOnce).to.be.true;
done();
}); });
});
it('handles unknown failure', function (done) { it('doesn\'t trigger notifications.showAPIError for other errors', function (done) {
server.post('/ghost/api/v0.1/uploads/', function () { let showAPIError = sinon.spy();
return [500, {'Content-Type': 'application/json'}, '']; this.set('notifications.showAPIError', showAPIError);
});
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => { stubFailedUpload(server, 400, 'UnknownError');
expect(this.$('.failed').length, 'error message is displayed').to.equal(1); this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
expect(this.$('.failed').text()).to.match(/Something went wrong/); fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
done();
}); wait().then(() => {
expect(showAPIError.called).to.be.false;
done();
}); });
});
it('triggers notifications.showAPIError for VersionMismatchError', function (done) { it('can be reset after a failed upload', function (done) {
let showAPIError = sinon.spy(); stubFailedUpload(server, 400, 'UnknownError');
this.set('notifications.showAPIError', showAPIError); this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'test.png'});
stubFailedUpload(server, 400, 'VersionMismatchError');
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(showAPIError.calledOnce).to.be.true;
done();
});
});
it('doesn\'t trigger notifications.showAPIError for other errors', function (done) {
let showAPIError = sinon.spy();
this.set('notifications.showAPIError', showAPIError);
stubFailedUpload(server, 400, 'UnknownError');
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(showAPIError.called).to.be.false;
done();
});
});
it('can be reset after a failed upload', function (done) {
stubFailedUpload(server, 400, 'UnknownError');
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'test.png'});
wait().then(() => {
run(() => {
this.$('.gh-btn-green').click();
});
});
wait().then(() => {
expect(this.$('input[type="file"]').length).to.equal(1);
done();
});
});
it('displays upload progress', function (done) {
this.set('done', done);
// pretender fires a progress event every 50ms
stubSuccessfulUpload(server, 150);
this.render(hbs`{{gh-image-uploader image=image uploadFinished=(action done) update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
// after 75ms we should have had one progress event
run.later(this, function () {
expect(this.$('.progress .bar').length).to.equal(1);
let [, percentageWidth] = this.$('.progress .bar').attr('style').match(/width: (\d+)%?/);
expect(percentageWidth).to.be.above(0);
expect(percentageWidth).to.be.below(100);
}, 75);
});
it('handles drag over/leave', function () {
stubSuccessfulUpload(server);
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
wait().then(() => {
run(() => { run(() => {
// eslint-disable-next-line new-cap this.$('.gh-btn-green').click();
let dragover = $.Event('dragover', {
dataTransfer: {
files: []
}
});
this.$('.gh-image-uploader').trigger(dragover);
}); });
expect(this.$('.gh-image-uploader').hasClass('-drag-over'), 'has drag-over class').to.be.true;
run(() => {
this.$('.gh-image-uploader').trigger('dragleave');
});
expect(this.$('.gh-image-uploader').hasClass('-drag-over'), 'has drag-over class').to.be.false;
}); });
it('triggers file upload on file drop', function (done) { wait().then(() => {
let uploadSuccess = sinon.spy(); expect(this.$('input[type="file"]').length).to.equal(1);
done();
});
});
it('displays upload progress', function (done) {
this.set('done', done);
// pretender fires a progress event every 50ms
stubSuccessfulUpload(server, 150);
this.render(hbs`{{gh-image-uploader image=image uploadFinished=(action done) update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
// after 75ms we should have had one progress event
run.later(this, function () {
expect(this.$('.progress .bar').length).to.equal(1);
let [, percentageWidth] = this.$('.progress .bar').attr('style').match(/width: (\d+)%?/);
expect(percentageWidth).to.be.above(0);
expect(percentageWidth).to.be.below(100);
}, 75);
});
it('handles drag over/leave', function () {
stubSuccessfulUpload(server);
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
run(() => {
// eslint-disable-next-line new-cap // eslint-disable-next-line new-cap
let drop = $.Event('drop', { let dragover = $.Event('dragover', {
dataTransfer: { dataTransfer: {
files: [createFile(['test'], {name: 'test.png'})] files: []
} }
}); });
this.$('.gh-image-uploader').trigger(dragover);
this.set('uploadSuccess', uploadSuccess);
stubSuccessfulUpload(server);
this.render(hbs`{{gh-image-uploader uploadSuccess=(action uploadSuccess)}}`);
run(() => {
this.$('.gh-image-uploader').trigger(drop);
});
wait().then(() => {
expect(uploadSuccess.calledOnce).to.be.true;
expect(uploadSuccess.firstCall.args[0]).to.equal('/content/images/test.png');
done();
});
}); });
it('validates extension by default', function (done) { expect(this.$('.gh-image-uploader').hasClass('-drag-over'), 'has drag-over class').to.be.true;
let uploadSuccess = sinon.spy();
let uploadFailed = sinon.spy();
this.set('uploadSuccess', uploadSuccess); run(() => {
this.set('uploadFailed', uploadFailed); this.$('.gh-image-uploader').trigger('dragleave');
stubSuccessfulUpload(server);
this.render(hbs`{{gh-image-uploader
uploadSuccess=(action uploadSuccess)
uploadFailed=(action uploadFailed)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.json'});
wait().then(() => {
expect(uploadSuccess.called).to.be.false;
expect(uploadFailed.calledOnce).to.be.true;
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
expect(this.$('.failed').text()).to.match(/The image type you uploaded is not supported/);
done();
});
}); });
it('uploads if validate action supplied and returns true', function (done) { expect(this.$('.gh-image-uploader').hasClass('-drag-over'), 'has drag-over class').to.be.false;
let validate = sinon.stub().returns(true); });
let uploadSuccess = sinon.spy();
this.set('validate', validate); it('triggers file upload on file drop', function (done) {
this.set('uploadSuccess', uploadSuccess); let uploadSuccess = sinon.spy();
// eslint-disable-next-line new-cap
stubSuccessfulUpload(server); let drop = $.Event('drop', {
dataTransfer: {
this.render(hbs`{{gh-image-uploader files: [createFile(['test'], {name: 'test.png'})]
uploadSuccess=(action uploadSuccess) }
validate=(action validate)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.txt'});
wait().then(() => {
expect(validate.calledOnce).to.be.true;
expect(uploadSuccess.calledOnce).to.be.true;
done();
});
}); });
it('skips upload and displays error if validate action supplied and doesn\'t return true', function (done) { this.set('uploadSuccess', uploadSuccess);
let validate = sinon.stub().returns(new UnsupportedMediaTypeError());
let uploadSuccess = sinon.spy();
let uploadFailed = sinon.spy();
this.set('validate', validate); stubSuccessfulUpload(server);
this.set('uploadSuccess', uploadSuccess); this.render(hbs`{{gh-image-uploader uploadSuccess=(action uploadSuccess)}}`);
this.set('uploadFailed', uploadFailed);
stubSuccessfulUpload(server); run(() => {
this.$('.gh-image-uploader').trigger(drop);
});
this.render(hbs`{{gh-image-uploader wait().then(() => {
uploadSuccess=(action uploadSuccess) expect(uploadSuccess.calledOnce).to.be.true;
uploadFailed=(action uploadFailed) expect(uploadSuccess.firstCall.args[0]).to.equal('/content/images/test.png');
validate=(action validate)}}`); done();
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(validate.calledOnce).to.be.true;
expect(uploadSuccess.called).to.be.false;
expect(uploadFailed.calledOnce).to.be.true;
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
expect(this.$('.failed').text()).to.match(/The image type you uploaded is not supported/);
done();
});
}); });
}); });
describe('URL input form', function () { it('validates extension by default', function (done) {
beforeEach(function () { let uploadSuccess = sinon.spy();
this.set('configService.fileStorage', false); let uploadFailed = sinon.spy();
this.set('uploadSuccess', uploadSuccess);
this.set('uploadFailed', uploadFailed);
stubSuccessfulUpload(server);
this.render(hbs`{{gh-image-uploader
uploadSuccess=(action uploadSuccess)
uploadFailed=(action uploadFailed)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.json'});
wait().then(() => {
expect(uploadSuccess.called).to.be.false;
expect(uploadFailed.calledOnce).to.be.true;
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
expect(this.$('.failed').text()).to.match(/The image type you uploaded is not supported/);
done();
}); });
});
it('displays save button by default', function () { it('uploads if validate action supplied and returns true', function (done) {
this.set('image', 'http://example.com/test.png'); let validate = sinon.stub().returns(true);
this.render(hbs`{{gh-image-uploader image=image text="text test"}}`); let uploadSuccess = sinon.spy();
expect(this.$('button').length).to.equal(1);
expect(this.$('input[type="text"]').val()).to.equal('http://example.com/test.png'); this.set('validate', validate);
this.set('uploadSuccess', uploadSuccess);
stubSuccessfulUpload(server);
this.render(hbs`{{gh-image-uploader
uploadSuccess=(action uploadSuccess)
validate=(action validate)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.txt'});
wait().then(() => {
expect(validate.calledOnce).to.be.true;
expect(uploadSuccess.calledOnce).to.be.true;
done();
}); });
});
it('can render without a save button', function () { it('skips upload and displays error if validate action supplied and doesn\'t return true', function (done) {
this.render(hbs`{{gh-image-uploader image=image saveButton=false text="text test"}}`); let validate = sinon.stub().returns(new UnsupportedMediaTypeError());
expect(this.$('button').length).to.equal(0); let uploadSuccess = sinon.spy();
expect(this.$('.description').text().trim()).to.equal('text test'); let uploadFailed = sinon.spy();
});
it('fires update action when save button clicked', function () { this.set('validate', validate);
let update = sinon.spy(); this.set('uploadSuccess', uploadSuccess);
this.set('update', update); this.set('uploadFailed', uploadFailed);
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`); stubSuccessfulUpload(server);
this.$('input[type="text"]').val('saved url'); this.render(hbs`{{gh-image-uploader
this.$('input[type="text"]').change(); uploadSuccess=(action uploadSuccess)
this.$('button.gh-btn-blue').click(); uploadFailed=(action uploadFailed)
validate=(action validate)}}`);
expect(update.calledOnce).to.be.true; fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
expect(update.firstCall.args[0]).to.equal('saved url');
});
it('fires onInput action when typing URL', function () { wait().then(() => {
let onInput = sinon.spy(); expect(validate.calledOnce).to.be.true;
this.set('onInput', onInput); expect(uploadSuccess.called).to.be.false;
expect(uploadFailed.calledOnce).to.be.true;
this.render(hbs`{{gh-image-uploader image=image onInput=(action onInput)}}`); expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
expect(this.$('.failed').text()).to.match(/The image type you uploaded is not supported/);
this.$('input[type="text"]').val('input url'); done();
this.$('input[type="text"]').change();
expect(onInput.calledOnce).to.be.true;
expect(onInput.firstCall.args[0]).to.equal('input url');
});
it('saves on enter key', function () {
let update = sinon.spy();
this.set('update', update);
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
this.$('input[type="text"]').val('saved url');
this.$('input[type="text"]').change();
this.$('input[type="text"]').trigger(
// eslint-disable-next-line new-cap
$.Event('keyup', {keyCode: keyCodes.enter, which: keyCodes.enter})
);
expect(update.calledOnce).to.be.true;
expect(update.firstCall.args[0]).to.equal('saved url');
}); });
}); });
}); });

View File

@ -61,17 +61,6 @@ describe('Integration: Component: gh-profile-image', function () {
expect(this.$()).to.have.length(1); expect(this.$()).to.have.length(1);
}); });
it('renders and tears down ok with fileStorage:false', function () {
this.set('fileStorage', false);
this.render(hbs`
{{gh-profile-image fileStorage=fileStorage}}
`);
expect(this.$()).to.have.length(1);
expect(this.$('input')).to.have.length(0);
}),
it('renders default image if no email supplied', function () { it('renders default image if no email supplied', function () {
this.set('email', null); this.set('email', null);