Commit ca1f2f77 by muhammad-ammar Committed by Andy Armstrong

Jasmine tests for upload/remove profile image on learner profile page.

TNL-1538
parent adf111e4
......@@ -53,6 +53,26 @@ class LearnerProfileViewTest(UrlResetMixin, TestCase):
reverse('preferences_api', kwargs={'username': self.user.username})
)
self.assertEqual(
context['data']['profile_image_upload_url'],
reverse("profile_image_upload", kwargs={'username': self.user.username})
)
self.assertEqual(
context['data']['profile_image_remove_url'],
reverse('profile_image_remove', kwargs={'username': self.user.username})
)
self.assertEqual(
context['data']['profile_image_max_bytes'],
settings.PROFILE_IMAGE_MAX_BYTES
)
self.assertEqual(
context['data']['profile_image_min_bytes'],
settings.PROFILE_IMAGE_MIN_BYTES
)
self.assertEqual(context['data']['account_settings_page_url'], reverse('account_settings'))
for attribute in self.CONTEXT_DATA:
......
......@@ -598,6 +598,7 @@
'lms/include/js/spec/views/fields_spec.js',
'lms/include/js/spec/student_profile/learner_profile_factory_spec.js',
'lms/include/js/spec/student_profile/learner_profile_view_spec.js',
'lms/include/js/spec/student_profile/learner_profile_fields_spec.js',
'lms/include/js/spec/verify_student/pay_and_verify_view_spec.js',
'lms/include/js/spec/verify_student/webcam_photo_view_spec.js',
'lms/include/js/spec/verify_student/image_input_spec.js',
......
......@@ -6,11 +6,15 @@ define(['underscore'], function(_) {
var IMAGE_UPLOAD_API_URL = '/api/profile_images/v0/staff/upload';
var IMAGE_REMOVE_API_URL = '/api/profile_images/v0/staff/remove';
var PROFILE_IMAGE = {
image_url_large: '/media/profile-images/image.jpg',
has_image: true
};
var USER_ACCOUNTS_DATA = {
username: 'student',
name: 'Student',
email: 'student@edx.org',
level_of_education: '0',
gender: '0',
year_of_birth: '0',
......@@ -18,7 +22,8 @@ define(['underscore'], function(_) {
language: '0',
bio: "About the student",
language_proficiencies: [{code: '1'}],
requires_parental_consent: true
requires_parental_consent: true,
profile_image: PROFILE_IMAGE
};
var USER_PREFERENCES_DATA = {
......@@ -101,6 +106,7 @@ define(['underscore'], function(_) {
IMAGE_REMOVE_API_URL: IMAGE_REMOVE_API_URL,
IMAGE_MAX_BYTES: IMAGE_MAX_BYTES,
IMAGE_MIN_BYTES: IMAGE_MIN_BYTES,
PROFILE_IMAGE: PROFILE_IMAGE,
USER_ACCOUNTS_DATA: USER_ACCOUNTS_DATA,
USER_PREFERENCES_DATA: USER_PREFERENCES_DATA,
FIELD_OPTIONS: FIELD_OPTIONS,
......
......@@ -9,10 +9,12 @@ define(['underscore'], function(_) {
expect(fieldTitle).toBe(view.options.title);
}
if ('fieldValue' in view) {
if ('fieldValue' in view || 'imageUrl' in view) {
expect(view.model.get(view.options.valueAttribute)).toBeTruthy();
if (view.fieldValue()) {
if ('imageUrl' in view) {
expect($($element.find('.image-frame')[0]).attr('src')).toBe(view.imageUrl());
} else if (view.fieldValue()) {
expect(view.fieldValue()).toBe(view.modelValue());
} else if ('optionForValue' in view) {
......@@ -43,9 +45,11 @@ define(['underscore'], function(_) {
var sectionOneFieldElements = $(learnerProfileView.$('.wrapper-profile-section-one')).find('.u-field');
expect(sectionOneFieldElements.length).toBe(learnerProfileView.options.sectionOneFieldViews.length);
expect(sectionOneFieldElements.length).toBe(4);
expectProfileElementContainsField(sectionOneFieldElements[0], learnerProfileView.options.profileImageFieldView);
expectProfileElementContainsField(sectionOneFieldElements[1], learnerProfileView.options.usernameFieldView);
_.each(sectionOneFieldElements, function (sectionFieldElement, fieldIndex) {
_.each(_.rest(sectionOneFieldElements, 2) , function (sectionFieldElement, fieldIndex) {
expectProfileElementContainsField(
sectionFieldElement,
learnerProfileView.options.sectionOneFieldViews[fieldIndex]
......@@ -79,13 +83,15 @@ define(['underscore'], function(_) {
var sectionOneFieldElements = $(learnerProfileView.$('.wrapper-profile-section-one')).find('.u-field');
expect(sectionOneFieldElements.length).toBe(1);
_.each(sectionOneFieldElements, function (sectionFieldElement, fieldIndex) {
expectProfileElementContainsField(
sectionFieldElement,
learnerProfileView.options.sectionOneFieldViews[fieldIndex]
);
});
expect(sectionOneFieldElements.length).toBe(2);
expectProfileElementContainsField(
sectionOneFieldElements[0],
learnerProfileView.options.profileImageFieldView
);
expectProfileElementContainsField(
sectionOneFieldElements[1],
learnerProfileView.options.usernameFieldView
);
if (othersProfile) {
expect($('.profile-private--message').text())
......
......@@ -6,7 +6,8 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
'js/student_account/models/user_preferences_model',
'js/student_profile/views/learner_profile_view',
'js/student_profile/views/learner_profile_fields',
'js/student_profile/views/learner_profile_factory'
'js/student_profile/views/learner_profile_factory',
'js/views/message_banner'
],
function (Backbone, $, _, AjaxHelpers, TemplateHelpers, Helpers, LearnerProfileHelpers, FieldViews,
UserAccountModel, UserPreferencesModel, LearnerProfileView, LearnerProfileFields, LearnerProfilePage) {
......@@ -17,10 +18,12 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
var requests;
beforeEach(function () {
setFixtures('<div class="wrapper-profile"><div class="ui-loading-indicator"><p><span class="spin"><i class="icon fa fa-refresh"></i></span> <span class="copy">Loading</span></p></div><div class="ui-loading-error is-hidden"><i class="fa fa-exclamation-triangle message-error" aria-hidden=true></i><span class="copy">An error occurred. Please reload the page.</span></div></div>');
setFixtures('<div class="message-banner"></div><div class="wrapper-profile"><div class="ui-loading-indicator"><p><span class="spin"><i class="icon fa fa-refresh"></i></span> <span class="copy">Loading</span></p></div><div class="ui-loading-error is-hidden"><i class="fa fa-exclamation-triangle message-error" aria-hidden=true></i><span class="copy">An error occurred. Please reload the page.</span></div></div>');
TemplateHelpers.installTemplate('templates/fields/field_readonly');
TemplateHelpers.installTemplate('templates/fields/field_dropdown');
TemplateHelpers.installTemplate('templates/fields/field_textarea');
TemplateHelpers.installTemplate('templates/fields/field_image');
TemplateHelpers.installTemplate('templates/fields/message_banner');
TemplateHelpers.installTemplate('templates/student_profile/learner_profile');
});
......@@ -32,7 +35,11 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
'account_settings_page_url': Helpers.USER_ACCOUNTS_API_URL,
'country_options': Helpers.FIELD_OPTIONS,
'language_options': Helpers.FIELD_OPTIONS,
'has_preferences_access': true
'has_preferences_access': true,
'profile_image_max_bytes': Helpers.IMAGE_MAX_BYTES,
'profile_image_min_bytes': Helpers.IMAGE_MIN_BYTES,
'profile_image_upload_url': Helpers.IMAGE_UPLOAD_API_URL,
'profile_image_remove_url': Helpers.IMAGE_REMOVE_API_URL
});
};
......
define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'js/common_helpers/template_helpers',
'js/spec/student_account/helpers',
'js/student_account/models/user_account_model',
'js/student_profile/views/learner_profile_fields',
'js/views/message_banner'
],
function (Backbone, $, _, AjaxHelpers, TemplateHelpers, Helpers, UserAccountModel, LearnerProfileFields,
MessageBannerView) {
'use strict';
describe("edx.user.LearnerProfileFields", function () {
var createImageView = function (ownProfile, hasImage, imageMaxBytes, imageMinBytes, yearOfBirth) {
var imageData = {
image_url_large: '/media/profile-images/default.jpg',
has_image: hasImage ? true : false
};
yearOfBirth = _.isUndefined(yearOfBirth) ? 1989 : yearOfBirth;
var accountSettingsModel = new UserAccountModel();
accountSettingsModel.set({'profile_image': imageData});
accountSettingsModel.set({'year_of_birth': yearOfBirth});
accountSettingsModel.set({'requires_parental_consent': _.isEmpty(yearOfBirth) ? true : false});
accountSettingsModel.url = Helpers.USER_ACCOUNTS_API_URL;
var messageView = new MessageBannerView({
el: $('.message-banner')
});
imageMaxBytes = imageMaxBytes || 64;
imageMinBytes = imageMinBytes || 16;
var editable = ownProfile ? 'toggle' : 'never';
return new LearnerProfileFields.ProfileImageFieldView({
model: accountSettingsModel,
valueAttribute: "profile_image",
editable: editable === 'toggle',
messageView: messageView,
imageMaxBytes: imageMaxBytes,
imageMinBytes: imageMinBytes,
imageUploadUrl: Helpers.IMAGE_UPLOAD_API_URL,
imageRemoveUrl: Helpers.IMAGE_REMOVE_API_URL
});
};
beforeEach(function () {
setFixtures('<div class="message-banner"></div><div class="wrapper-profile"><div class="ui-loading-indicator"><p><span class="spin"><i class="icon fa fa-refresh"></i></span> <span class="copy">Loading</span></p></div><div class="ui-loading-error is-hidden"><i class="fa fa-exclamation-triangle message-error" aria-hidden=true></i><span class="copy">An error occurred. Please reload the page.</span></div></div>');
TemplateHelpers.installTemplate('templates/student_profile/learner_profile');
TemplateHelpers.installTemplate('templates/fields/field_image');
TemplateHelpers.installTemplate("templates/fields/message_banner");
});
var createFakeImageFile = function (size) {
var fileFakeData = 'i63ljc6giwoskyb9x5sw0169bdcmcxr3cdz8boqv0lik971972cmd6yknvcxr5sw0nvc169bdcmcxsdf';
return new Blob(
[ fileFakeData.substr(0, size) ],
{ type: 'image/jpg' }
);
};
it("can upload profile image", function() {
var imageView = createImageView(true, false);
imageView.render();
var requests = AjaxHelpers.requests(this);
var imageName = 'profile_image.jpg';
// Initialize jquery file uploader
imageView.$('.upload-button-input').fileupload({
url: Helpers.IMAGE_UPLOAD_API_URL,
type: 'POST',
add: imageView.fileSelected,
done: imageView.imageChangeSucceeded,
fail: imageView.imageChangeFailed
});
// Remove button should not be present for default image
expect(imageView.$('.u-field-remove-button').css('display') === 'none').toBeTruthy();
// For default image, image title should be `Upload an image`
expect(imageView.$('.upload-button-title').text().trim()).toBe(imageView.titleAdd);
// Add image to upload queue, this will validate the image size and send POST request to upload image
imageView.$('.upload-button-input').fileupload('add', {files: [createFakeImageFile(60)]});
// Verify image upload progress message
expect(imageView.$('.upload-button-title').text().trim()).toBe(imageView.titleUploading);
// Verify if POST request received for image upload
AjaxHelpers.expectRequest(requests, 'POST', Helpers.IMAGE_UPLOAD_API_URL, new FormData());
// Send 204 NO CONTENT to confirm the image upload success
AjaxHelpers.respondWithNoContent(requests);
// Upon successful image upload, account settings model will be fetched to get the url for newly uploaded image
// So we need to send the response for that GET
var data = {profile_image: {
image_url_large: '/media/profile-images/' + imageName,
has_image: true
}};
AjaxHelpers.respondWithJson(requests, data);
// Verify uploaded image name
expect(imageView.$('.image-frame').attr('src')).toContain(imageName);
// Remove button should be present after successful image upload
expect(imageView.$('.u-field-remove-button').css('display') !== 'none').toBeTruthy();
// After image upload, image title should be `Change image`
expect(imageView.$('.upload-button-title').text().trim()).toBe(imageView.titleEdit);
});
it("can remove profile image", function() {
var imageView = createImageView(true, true);
imageView.render();
var requests = AjaxHelpers.requests(this);
imageView.$('.u-field-remove-button').click();
// Verify image remove progress message
expect(imageView.$('.remove-button-title').text().trim()).toBe(imageView.titleRemoving);
// Verify if POST request received for image remove
AjaxHelpers.expectRequest(requests, 'POST', Helpers.IMAGE_REMOVE_API_URL, null);
// Send 204 NO CONTENT to confirm the image removal success
AjaxHelpers.respondWithNoContent(requests);
// Upon successful image removal, account settings model will be fetched to get the default image url
// So we need to send the response for that GET
var data = {profile_image: {
image_url_large: '/media/profile-images/default.jpg',
has_image: false
}};
AjaxHelpers.respondWithJson(requests, data);
// Remove button should not be present for default image
expect(imageView.$('.u-field-remove-button').css('display') === 'none').toBeTruthy();
});
it("can't remove default profile image", function() {
var imageView = createImageView(true, false);
imageView.render();
spyOn(imageView, 'clickedRemoveButton');
// Remove button should not be present for default image
expect(imageView.$('.u-field-remove-button').css('display') === 'none').toBeTruthy();
imageView.$('.u-field-remove-button').click();
// Remove button click handler should not be called
expect(imageView.clickedRemoveButton).not.toHaveBeenCalled();
});
it("can't upload image having size greater than max size", function() {
var imageView = createImageView(true, false);
imageView.render();
// Initialize jquery file uploader
imageView.$('.upload-button-input').fileupload({
url: Helpers.IMAGE_UPLOAD_API_URL,
type: 'POST',
add: imageView.fileSelected,
done: imageView.imageChangeSucceeded,
fail: imageView.imageChangeFailed
});
// Add image to upload queue, this will validate the image size
imageView.$('.upload-button-input').fileupload('add', {files: [createFakeImageFile(70)]});
// Verify error message
expect($('.message-banner').text().trim()).toBe('Your image must be smaller than 64 Bytes in size.');
});
it("can't upload image having size less than min size", function() {
var imageView = createImageView(true, false);
imageView.render();
// Initialize jquery file uploader
imageView.$('.upload-button-input').fileupload({
url: Helpers.IMAGE_UPLOAD_API_URL,
type: 'POST',
add: imageView.fileSelected,
done: imageView.imageChangeSucceeded,
fail: imageView.imageChangeFailed
});
// Add image to upload queue, this will validate the image size
imageView.$('.upload-button-input').fileupload('add', {files: [createFakeImageFile(10)]});
// Verify error message
expect($('.message-banner').text().trim()).toBe('Your image must be at least 16 Bytes in size.');
});
it("can't upload/remove image if parental consent required", function() {
var imageView = createImageView(true, false, 64, 16, '');
imageView.render();
spyOn(imageView, 'clickedUploadButton');
spyOn(imageView, 'clickedRemoveButton');
expect(imageView.$('.u-field-upload-button').css('display') === 'none').toBeTruthy();
expect(imageView.$('.u-field-remove-button').css('display') === 'none').toBeTruthy();
imageView.$('.u-field-upload-button').click();
imageView.$('.u-field-remove-button').click();
expect(imageView.clickedUploadButton).not.toHaveBeenCalled();
expect(imageView.clickedRemoveButton).not.toHaveBeenCalled();
});
it("can't upload image on others profile", function() {
var imageView = createImageView(false);
imageView.render();
spyOn(imageView, 'clickedUploadButton');
spyOn(imageView, 'clickedRemoveButton');
expect(imageView.$('.u-field-upload-button').css('display') === 'none').toBeTruthy();
expect(imageView.$('.u-field-remove-button').css('display') === 'none').toBeTruthy();
imageView.$('.u-field-upload-button').click();
imageView.$('.u-field-remove-button').click();
expect(imageView.clickedUploadButton).not.toHaveBeenCalled();
expect(imageView.clickedRemoveButton).not.toHaveBeenCalled();
});
it("shows message if we try to navigate away during image upload/remove", function() {
var imageView = createImageView(true, false);
spyOn(imageView, 'onBeforeUnload');
imageView.render();
// Initialize jquery file uploader
imageView.$('.upload-button-input').fileupload({
url: Helpers.IMAGE_UPLOAD_API_URL,
type: 'POST',
add: imageView.fileSelected,
done: imageView.imageChangeSucceeded,
fail: imageView.imageChangeFailed
});
// Add image to upload queue, this will validate the image size and send POST request to upload image
imageView.$('.upload-button-input').fileupload('add', {files: [createFakeImageFile(60)]});
// Verify image upload progress message
expect(imageView.$('.upload-button-title').text().trim()).toBe(imageView.titleUploading);
$(window).trigger('beforeunload');
expect(imageView.onBeforeUnload).toHaveBeenCalled();
});
it('renders message correctly', function() {
var messageSelector = '.message-banner';
var messageView = new MessageBannerView({
el: $(messageSelector)
});
messageView.showMessage('I am message view');
// Verify error message
expect($(messageSelector).text().trim()).toBe('I am message view');
messageView.hideMessage();
expect($(messageSelector).text().trim()).toBe('');
});
});
});
......@@ -24,6 +24,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
accountSettingsData.requires_parental_consent = false;
accountSettingsModel.set(accountSettingsData);
accountSettingsModel.set({'profile_is_public': profileIsPublic});
accountSettingsModel.set({'profile_image': Helpers.PROFILE_IMAGE});
var accountPreferencesModel = new AccountPreferencesModel();
accountPreferencesModel.set({account_privacy: accountPrivacy});
......@@ -51,7 +52,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
el: $('.message-banner')
});
var profileImageFieldView = new FieldsView.ImageFieldView({
var profileImageFieldView = new LearnerProfileFields.ProfileImageFieldView({
model: accountSettingsModel,
valueAttribute: "profile_image",
editable: editable,
......@@ -69,7 +70,6 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
});
var sectionOneFieldViews = [
usernameFieldView,
new FieldViews.DropdownFieldView({
model: accountSettingsModel,
required: false,
......@@ -117,6 +117,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
preferencesModel: accountPreferencesModel,
accountPrivacyFieldView: accountPrivacyFieldView,
usernameFieldView: usernameFieldView,
profileImageFieldView: profileImageFieldView,
sectionOneFieldViews: sectionOneFieldViews,
sectionTwoFieldViews: sectionTwoFieldViews
});
......@@ -128,7 +129,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
TemplateHelpers.installTemplate('templates/fields/field_dropdown');
TemplateHelpers.installTemplate('templates/fields/field_textarea');
TemplateHelpers.installTemplate('templates/fields/field_image');
TemplateHelpers.installTemplate('templates/message_banner');
TemplateHelpers.installTemplate('templates/fields/message_banner');
TemplateHelpers.installTemplate('templates/student_profile/learner_profile');
});
......
......@@ -70,9 +70,14 @@
imageChangeFailed: function (e, data) {
this.setCurrentStatus('');
if (_.contains([400, 404], data.jqXHR.status)) {
this.showImageChangeFailedMessage(data.jqXHR.status, data.jqXHR.responseText);
this.render();
},
showImageChangeFailedMessage: function (status, responseText) {
if (_.contains([400, 404], status)) {
try {
var errors = JSON.parse(data.jqXHR.responseText);
var errors = JSON.parse(responseText);
this.showErrorMessage(errors.user_message);
} catch (error) {
this.showErrorMessage(this.errorMessage);
......@@ -80,7 +85,6 @@
} else {
this.showErrorMessage(this.errorMessage);
}
this.render();
},
showErrorMessage: function (message) {
......
......@@ -548,7 +548,8 @@
return this;
},
showErrorMessage: function () {
showErrorMessage: function (message) {
return message;
},
imageUrl: function () {
......@@ -593,7 +594,7 @@
}
},
clickedUploadButton: function () {
clickedUploadButton: function (e, data) {
$(this.uploadButtonSelector).fileupload({
url: this.options.imageUploadUrl,
type: 'POST',
......@@ -603,7 +604,7 @@
});
},
clickedRemoveButton: function () {
clickedRemoveButton: function (e, data) {
var view = this;
this.setCurrentStatus('removing');
this.setUploadButtonVisibility('none');
......@@ -615,7 +616,7 @@
view.imageChangeSucceeded();
},
error: function (xhr, status, error) {
view.imageChangeFailed();
view.showImageChangeFailedMessage(xhr.status, xhr.responseText);
}
});
},
......@@ -627,8 +628,11 @@
imageChangeFailed: function (e, data) {
},
showImageChangeFailedMessage: function (status, responseText) {
},
fileSelected: function (e, data) {
if (this.validateImageSize(data.files[0].size)) {
if (_.isUndefined(data.files[0].size) || this.validateImageSize(data.files[0].size)) {
data.formData = {file: data.files[0]};
this.setCurrentStatus('uploading');
this.setRemoveButtonVisibility('none');
......@@ -681,6 +685,7 @@
},
onBeforeUnload: function () {
console.log('Do you really want to go away?');
var status = this.getCurrentStatus();
if (status === 'uploading') {
return gettext("Upload is in progress. To avoid errors, stay on this page until the process is complete.");
......
......@@ -30,6 +30,7 @@
background: transparent !important;
border: none !important;
padding: 0;
-webkit-tap-highlight-color: transparent;
}
.u-field-image {
......
......@@ -10,7 +10,7 @@
<%block name="bodyclass">view-profile</%block>
<%block name="header_extras">
% for template_name in ["field_dropdown", "field_image", "field_textarea", "field_readonly"]:
% for template_name in ["field_dropdown", "field_image", "field_textarea", "field_readonly", "message_banner"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="fields/${template_name}.underscore" />
</script>
......@@ -21,12 +21,6 @@
<%static:include path="student_profile/${template_name}.underscore" />
</script>
% endfor
% for template_name in ["message_banner"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="${template_name}.underscore" />
</script>
% endfor
</%block>
<div class="message-banner" aria-live="polite"></div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment