diff --git a/app/jsx/course_settings/actions.jsx b/app/jsx/course_settings/actions.jsx index e2105d6b9c4..f1987c9e818 100644 --- a/app/jsx/course_settings/actions.jsx +++ b/app/jsx/course_settings/actions.jsx @@ -76,9 +76,37 @@ define ([ }; }, - prepareSetImage (imageUrl, imageId, ajaxLib = axios) { + putImageData(courseId, imageUrl, imageId = null, ajaxLib = axios) { + const data = imageId ? {"course[image_id]": imageId} : + {"course[image_url]": imageUrl}; + + return (dispatch, getState) => { + this.ajaxPutFormData(`/api/v1/courses/${courseId}`, data, ajaxLib) + .then((response)=> { + dispatch(imageId ? this.setCourseImageId(imageUrl, imageId) : + this.setCourseImageUrl(imageUrl)); + }) + .catch((response) => { + this.errorUploadingImage(); + }) + } + }, + + putRemoveImage(courseId, ajaxLib = axios) { + return (dispatch, getState) => { + this.ajaxPutFormData(`/api/v1/courses/${courseId}`, {"course[remove_image]": true}, ajaxLib) + .then((response)=> { + dispatch(this.removeImage()); + }) + .catch((response) => { + $.flashError(I18n.t("Error removing image")); + }) + } + }, + + prepareSetImage (imageUrl, imageId, courseId, ajaxLib = axios) { if (imageUrl) { - return this.setCourseImageId(imageUrl, imageId); + return this.putImageData(courseId, imageUrl, imageId, ajaxLib); } else { // In this case the url field was blank so we could either // recreate it or hit the API to get it. We hit the api @@ -86,7 +114,7 @@ define ([ return (dispatch, getState) => { ajaxLib.get(`/api/v1/files/${imageId}`) .then((response) => { - dispatch(this.setCourseImageId(response.data.url, imageId)); + dispatch(this.putImageData(courseId, response.data.url, imageId, ajaxLib)); }) .catch((response) => { this.errorUploadingImage(); @@ -119,7 +147,7 @@ define ([ formData.append('file', file); ajaxLib.post(response.data.upload_url, formData) .then((response) => { - dispatch(this.prepareSetImage(response.data.url, response.data.id)); + dispatch(this.prepareSetImage(response.data.url, response.data.id, courseId, ajaxLib)); }) .catch((response) => { this.errorUploadingImage(); @@ -133,6 +161,22 @@ define ([ $.flashWarning(I18n.t("'%{type}' is not a valid image type (try jpg, png, or gif)", {type})); } }; + }, + + ajaxPutFormData(path, data, ajaxLib = axios) { + return ( + ajaxLib.put(path, data, + { + // TODO: this is a naive implementation, + // upgrading to axios@0.12.0 will make it unnecessary + // by using URLSearchParams. + transformRequest: function (data, headers) { + return Object.keys(data).reduce((prev, key) => { + return prev + (prev ? '&' : '') + `${key}=${data[key]}`; + }, ''); + } + }) + ); } }; diff --git a/app/jsx/course_settings/components/CourseImageSelector.jsx b/app/jsx/course_settings/components/CourseImageSelector.jsx index 9e3e9542764..49f3d5704e4 100644 --- a/app/jsx/course_settings/components/CourseImageSelector.jsx +++ b/app/jsx/course_settings/components/CourseImageSelector.jsx @@ -52,7 +52,7 @@ define([ } removeImage() { - this.props.store.dispatch(Actions.removeImage()); + this.props.store.dispatch(Actions.putRemoveImage(this.props.courseId)); } imageControls () { @@ -130,16 +130,8 @@ define([ backgroundImage: `url(${this.state.imageUrl})` }; - var value = this.state.removeImage ? true : this.state.courseImage; - return (
-
this.props.store.dispatch(Actions.uploadFile(e, courseId))} - handleFlickrUrlUpload={(flickrUrl) => this.props.store.dispatch(Actions.setCourseImageUrl(flickrUrl))} + handleFlickrUrlUpload={(flickrUrl) => this.props.store.dispatch(Actions.putImageData(this.props.courseId, flickrUrl))} />
diff --git a/app/jsx/course_settings/reducer.jsx b/app/jsx/course_settings/reducer.jsx index 84b5c222f60..5702655ba6b 100644 --- a/app/jsx/course_settings/reducer.jsx +++ b/app/jsx/course_settings/reducer.jsx @@ -19,23 +19,17 @@ define([ state.imageUrl = action.payload.imageUrl; state.courseImage = action.payload.imageId; state.showModal = false; - state.removeImage = false; - state.hiddenInputName = "course[image_id]" return state; }, SET_COURSE_IMAGE_URL (state, action) { state.imageUrl = action.payload.imageUrl; state.courseImage = action.payload.imageUrl; state.showModal = false; - state.removeImage = false; - state.hiddenInputName = "course[image_url]"; return state; }, REMOVE_IMAGE (state) { state.imageUrl = ''; state.courseImage = 'abc'; - state.removeImage = true; - state.hiddenInputName = "course[remove_image]"; return state; } }; diff --git a/app/jsx/course_settings/store/initialState.jsx b/app/jsx/course_settings/store/initialState.jsx index a4d3a850296..40f4d3b767d 100644 --- a/app/jsx/course_settings/store/initialState.jsx +++ b/app/jsx/course_settings/store/initialState.jsx @@ -5,8 +5,6 @@ define([], () => { imageUrl: '', showModal: false, gettingImage: false, - removeImage: false, - hiddenInputName: '' }; return initialState; diff --git a/spec/javascripts/jsx/course_settings/actionsSpec.jsx b/spec/javascripts/jsx/course_settings/actionsSpec.jsx index 0f44ebea0e9..7ba645e03ff 100644 --- a/spec/javascripts/jsx/course_settings/actionsSpec.jsx +++ b/spec/javascripts/jsx/course_settings/actionsSpec.jsx @@ -77,10 +77,11 @@ define([ deepEqual(actual, expected, 'the objects match'); }); - test('prepareSetImage with a imageUrl calls setCourseImageId', () => { - sinon.spy(Actions, 'setCourseImageId'); - Actions.prepareSetImage('http://imageUrl', 12); - ok(Actions.setCourseImageId.called, 'setCourseImageId was called'); + test('prepareSetImage with a imageUrl calls putImageData', () => { + sinon.spy(Actions, 'putImageData'); + Actions.prepareSetImage('http://imageUrl', 12, 0); + ok(Actions.putImageData.called, 'putImageData was called'); + Actions.putImageData.restore(); }); asyncTest('prepareSetImage without a imageUrl calls the API to get the url', () => { @@ -98,17 +99,12 @@ define([ } }; - const expectedAction = { - type: 'SET_COURSE_IMAGE_ID', - payload: { - imageUrl: 'http://imageUrl', - imageId: 1 - } - }; + sinon.spy(Actions, 'putImageData'); - Actions.prepareSetImage(null, 1, fakeAjaxLib)((dispatched) => { + Actions.prepareSetImage(null, 1, 0, fakeAjaxLib)((dispatched) => { start(); - deepEqual(dispatched, expectedAction, 'the proper action was dispatched'); + ok(Actions.putImageData.called, 'putImageData was called indicating successfully hit API'); + Actions.putImageData.restore(); }); }); @@ -177,17 +173,12 @@ define([ preventDefault: () => {} }; - const expectedAction = { - type: 'SET_COURSE_IMAGE_ID', - payload: { - imageUrl: 'http://fileDownloadUrl', - imageId: 1 - } - }; + sinon.spy(Actions, 'prepareSetImage'); Actions.uploadFile(fakeDragonDropEvent, 1, fakeAjaxLib)((dispatched) => { start(); - deepEqual(dispatched, expectedAction, 'the SET_COURSE_IMAGE_ID action was fired'); + ok(Actions.prepareSetImage.called, 'prepareSetImage was called'); + Actions.prepareSetImage.restore(); }); }) diff --git a/spec/javascripts/jsx/course_settings/components/CourseImageSelectorSpec.jsx b/spec/javascripts/jsx/course_settings/components/CourseImageSelectorSpec.jsx index a4bdca5aa63..1ce6a0fa71d 100644 --- a/spec/javascripts/jsx/course_settings/components/CourseImageSelectorSpec.jsx +++ b/spec/javascripts/jsx/course_settings/components/CourseImageSelectorSpec.jsx @@ -23,13 +23,6 @@ define([ ok(component); }); - test('the hidden input reflects the state value of the selector', () => { - const component = TestUtils.renderIntoDocument( - - ); - equal(React.findDOMNode(component.refs.hiddenInput).value, initialState.courseImage, 'the input matches'); - }); - test('it sets the background image style properly', () => { const dispatchStub = sinon.stub(fakeStore, 'getState').returns(Object.assign(initialState, { imageUrl: 'http://coolUrl' diff --git a/spec/javascripts/jsx/course_settings/reducersSpec.jsx b/spec/javascripts/jsx/course_settings/reducersSpec.jsx index 07e6514a5dd..cab4a74129e 100644 --- a/spec/javascripts/jsx/course_settings/reducersSpec.jsx +++ b/spec/javascripts/jsx/course_settings/reducersSpec.jsx @@ -64,34 +64,12 @@ define(['jsx/course_settings/reducer'], (reducer) => { imageUrl: '', courseImage: '', showModal: true, - hiddenInputName: '' }; const newState = reducer(initialState, action); equal(newState.imageUrl, 'http://imageUrl', 'image url gets set'); equal(newState.courseImage, '42', 'image id gets set'); equal(newState.showModal, false, 'modal gets closed'); - equal(newState.hiddenInputName, 'course[image_id]', 'input name is set properly'); }); - - test('REMOVE_IMAGE', () => { - const action = { - type: 'REMOVE_IMAGE' - }; - - const initialState = { - imageUrl: 'http://imageUrl', - courseImage: '42', - removeImage: false, - hiddenInputName: '' - }; - - const newState = reducer(initialState, action); - equal(newState.imageUrl, '', 'image url gets set'); - equal(newState.courseImage, 'abc', 'image id gets set'); - equal(newState.removeImage, true, 'remove image is set'); - equal(newState.hiddenInputName, 'course[remove_image]', 'input name is set properly'); - }); - - + }); \ No newline at end of file