Commit c2d83bd4 by muzaffaryousaf Committed by Andy Armstrong

Bok_choy tests for upload/remove profile image.

TNL-1538
parent ca1f2f77
......@@ -5,6 +5,8 @@ from . import BASE_URL
from bok_choy.page_object import PageObject
from .fields import FieldsMixin
from bok_choy.promise import EmptyPromise
from .instructor_dashboard import InstructorDashboardPage
from selenium.webdriver import ActionChains
PROFILE_VISIBILITY_SELECTOR = '#u-field-select-account_privacy option[value="{}"]'
......@@ -165,3 +167,109 @@ class LearnerProfilePage(FieldsMixin, PageObject):
"""
self.wait_for_ajax()
return self.q(css='#u-field-message-account_privacy').visible
@property
def profile_has_default_image(self):
"""
Return bool if image field has default photo or not.
"""
self.wait_for_field('image')
default_links = self.q(css='.image-frame').attrs('src')
return 'default-profile' in default_links[0] if default_links else False
def mouse_hover(self, element):
"""
Mouse over on given element.
"""
mouse_hover_action = ActionChains(self.browser).move_to_element(element)
mouse_hover_action.perform()
def profile_has_image_with_public_access(self):
"""
Check if image is present with remove/upload access.
"""
self.wait_for_field('image')
self.mouse_hover(self.browser.find_element_by_css_selector('.image-wrapper'))
self.wait_for_element_visibility('.u-field-upload-button', "upload button is visible")
return self.q(css='.u-field-upload-button').visible
def profile_has_image_with_private_access(self):
"""
Check if image is present with remove/upload access.
"""
self.wait_for_field('image')
return self.q(css='.u-field-upload-button').visible
def upload_file(self, filename):
"""
Helper method to upload an image file.
"""
self.wait_for_element_visibility('.u-field-upload-button', "upload button is visible")
file_path = InstructorDashboardPage.get_asset_path(filename)
# make the elements visible.
self.browser.execute_script('$(".u-field-upload-button").css("opacity",1);')
self.browser.execute_script('$(".upload-button-input").css("opacity",1);')
self.wait_for_element_visibility('.upload-button-input', "upload button is visible")
self.browser.execute_script('$(".upload-submit").show();')
# First send_keys will initialize the jquery auto upload plugin.
self.q(css='.upload-button-input').results[0].send_keys(file_path)
self.q(css='.upload-submit').first.click()
self.q(css='.upload-button-input').results[0].send_keys(file_path)
self.wait_for_ajax()
def upload_correct_image_file(self, filename):
"""
Selects the correct file and clicks the upload button.
"""
self._upload_file(filename)
@property
def image_upload_success(self):
"""
Returns the bool, if image is updated or not.
"""
self.wait_for_field('image')
self.wait_for_ajax()
self.wait_for_element_visibility('.image-frame', "image box is visible")
image_link = self.q(css='.image-frame').attrs('src')
return 'default-profile' not in image_link[0]
@property
def profile_image_message(self):
"""
Returns the text message for profile image.
"""
self.wait_for_field('image')
self.wait_for_ajax()
return self.q(css='.message-banner p').text[0]
def remove_profile_image(self):
"""
Removes the profile image.
"""
self.wait_for_field('image')
self.wait_for_ajax()
self.wait_for_element_visibility('.image-wrapper', "remove button is visible")
self.browser.execute_script('$(".u-field-remove-button").css("opacity",1);')
self.mouse_hover(self.browser.find_element_by_css_selector('.image-wrapper'))
self.wait_for_element_visibility('.u-field-remove-button', "remove button is visible")
self.q(css='.u-field-remove-button').first.click()
self.mouse_hover(self.browser.find_element_by_css_selector('.image-wrapper'))
self.wait_for_element_visibility('.u-field-upload-button', "upload button is visible")
return True
@property
def remove_link_present(self):
self.wait_for_field('image')
self.mouse_hover(self.browser.find_element_by_css_selector('.image-wrapper'))
return self.q(css='.u-field-remove-button').visible
......@@ -111,6 +111,13 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
self.assertEqual(profile_page.age_limit_message_present, message is not None)
self.assertIn(message, profile_page.profile_forced_private_message)
def assert_default_image_has_public_access(self, profile_page):
"""
Assert that profile image has public access.
"""
self.assertTrue(profile_page.profile_has_default_image)
self.assertTrue(profile_page.profile_has_image_with_public_access())
def test_dashboard_learner_profile_link(self):
"""
Scenario: Verify that my profile link is present on dashboard page and we can navigate to correct page.
......@@ -320,6 +327,171 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
)
self.verify_profile_page_view_event(user_id, visibility=self.PRIVACY_PRIVATE)
def test_user_can_only_see_default_image_for_private_profile(self):
"""
Scenario: Default profile image behaves correctly for under age user.
Given that I am on my profile page with private access
And I can see default image
When I move my cursor to the image
Then i cannot see the upload/remove image text
And i cannot upload/remove the image.
"""
year_of_birth = datetime.now().year - 5
username, user_id = self.log_in_as_unique_user()
profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PRIVATE)
self.verify_profile_forced_private_message(
username,
year_of_birth,
message='You must be over 13 to share a full profile.'
)
self.assertTrue(profile_page.profile_has_default_image)
self.assertFalse(profile_page.profile_has_image_with_private_access())
def test_user_can_see_default_image_for_public_profile(self):
"""
Scenario: Default profile image behaves correctly for public profile.
Given that I am on my profile page with public access
And I can see default image
When I move my cursor to the image
Then i can see the upload/remove image text
And i am able to upload new image
"""
username, user_id = self.log_in_as_unique_user()
profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC)
self.assert_default_image_has_public_access(profile_page)
def test_user_can_upload_the_profile_image_with_success(self):
"""
Scenario: Upload profile image works correctly.
Given that I am on my profile page with public access
And I can see default image
When I move my cursor to the image
Then i can see the upload/remove image text
When i upload new image via file uploader
Then i can see the changed image
And i can also see the latest image after reload.
"""
username, user_id = self.log_in_as_unique_user()
profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC)
self.assert_default_image_has_public_access(profile_page)
profile_page.upload_file(filename='image.jpg')
self.assertTrue(profile_page.image_upload_success)
profile_page.visit()
self.assertTrue(profile_page.image_upload_success)
def test_user_can_see_error_for_exceeding_max_file_size_limit(self):
"""
Scenario: Upload profile image does not work for > 1MB image file.
Given that I am on my profile page with public access
And I can see default image
When I move my cursor to the image
Then i can see the upload/remove image text
When i upload new > 1MB image via file uploader
Then i can see the error message for file size limit
And i can still see the default image after page reload.
"""
username, user_id = self.log_in_as_unique_user()
profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC)
self.assert_default_image_has_public_access(profile_page)
profile_page.upload_file(filename='larger_image.jpg')
self.assertEqual(profile_page.profile_image_message, "Your image must be smaller than 1 MB in size.")
profile_page.visit()
self.assertTrue(profile_page.profile_has_default_image)
def test_user_can_see_error_for_file_size_below_the_min_limit(self):
"""
Scenario: Upload profile image does not work for < 100 Bytes image file.
Given that I am on my profile page with public access
And I can see default image
When I move my cursor to the image
Then i can see the upload/remove image text
When i upload new < 100 Bytes image via file uploader
Then i can see the error message for minimum file size limit
And i can still see the default image after page reload.
"""
username, user_id = self.log_in_as_unique_user()
profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC)
self.assert_default_image_has_public_access(profile_page)
profile_page.upload_file(filename='list-icon-visited.png')
self.assertEqual(profile_page.profile_image_message, "Your image must be at least 100 bytes in size.")
profile_page.visit()
self.assertTrue(profile_page.profile_has_default_image)
def test_user_can_see_error_for_wrong_file_type(self):
"""
Scenario: Upload profile image does not work for wrong file types.
Given that I am on my profile page with public access
And I can see default image
When I move my cursor to the image
Then i can see the upload/remove image text
When i upload new csv file via file uploader
Then i can see the error message for wrong/unsupported file type
And i can still see the default image after page reload.
"""
username, user_id = self.log_in_as_unique_user()
profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC)
self.assert_default_image_has_public_access(profile_page)
profile_page.upload_file(filename='cohort_users_only_username.csv')
self.assertEqual(profile_page.profile_image_message, "Unsupported file type.")
profile_page.visit()
self.assertTrue(profile_page.profile_has_default_image)
def test_user_can_remove_profile_image(self):
"""
Scenario: Remove profile image works correctly.
Given that I am on my profile page with public access
And I can see default image
When I move my cursor to the image
Then i can see the upload/remove image text
When i click on the remove image link
Then i can see the default image
And i can still see the default image after page reload.
"""
username, user_id = self.log_in_as_unique_user()
profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC)
self.assert_default_image_has_public_access(profile_page)
profile_page.upload_file(filename='image.jpg')
self.assertTrue(profile_page.image_upload_success)
self.assertTrue(profile_page.remove_profile_image())
self.assertTrue(profile_page.profile_has_default_image)
profile_page.visit()
self.assertTrue(profile_page.profile_has_default_image)
def test_user_cannot_remove_default_image(self):
"""
Scenario: Remove profile image does not works for default images.
Given that I am on my profile page with public access
And I can see default image
When I move my cursor to the image
Then i can see only the upload image text
And i cannot see the remove image text
"""
username, user_id = self.log_in_as_unique_user()
profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC)
self.assert_default_image_has_public_access(profile_page)
self.assertFalse(profile_page.remove_link_present)
class DifferentUserLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
"""
......
......@@ -131,6 +131,14 @@ MOCK_SEARCH_BACKING_FILE = (
import uuid
SECRET_KEY = uuid.uuid4().hex
# Set dummy values for profile image settings.
PROFILE_IMAGE_BACKEND = {
'class': 'storages.backends.overwrite.OverwriteStorage',
'options': {
'location': os.path.join(MEDIA_ROOT, 'profile-images/'),
'base_url': os.path.join(MEDIA_URL, 'profile-images/'),
},
}
#####################################################################
# Lastly, see if the developer has any local overrides.
try:
......
<div class="message-banner" aria-live="polite"></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>
......@@ -18,7 +18,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
var requests;
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>');
loadFixtures('js/fixtures/student_profile/student_profile.html');
TemplateHelpers.installTemplate('templates/fields/field_readonly');
TemplateHelpers.installTemplate('templates/fields/field_dropdown');
TemplateHelpers.installTemplate('templates/fields/field_textarea');
......
......@@ -124,7 +124,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
};
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>');
loadFixtures('js/fixtures/student_profile/student_profile.html');
TemplateHelpers.installTemplate('templates/fields/field_readonly');
TemplateHelpers.installTemplate('templates/fields/field_dropdown');
TemplateHelpers.installTemplate('templates/fields/field_textarea');
......
define(['backbone', 'jquery', 'underscore', 'js/views/message_banner'
],
function (Backbone, $, _, MessageBannerView) {
'use strict';
describe("MessageBannerView", function () {
beforeEach(function () {
setFixtures('<div class="message-banner"></div>');
TemplateHelpers.installTemplate("templates/fields/message_banner");
});
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('');
});
});
});
......@@ -9,7 +9,7 @@
'js/student_profile/views/learner_profile_view',
'js/student_account/views/account_settings_fields',
'js/views/message_banner'
], function (gettext, $, _, Backbone, AccountSettingsModel, AccountPreferencesModel, FieldsView,
], function (gettext, $, _, Backbone, Logger, AccountSettingsModel, AccountPreferencesModel, FieldsView,
LearnerProfileFieldsView, LearnerProfileView, AccountSettingsFieldViews, MessageBannerView) {
return function (options) {
......
......@@ -48,21 +48,15 @@
this.$('.profile-section-one-fields').append(this.options.usernameFieldView.render().el);
var imageView = this.options.profileImageFieldView;
imageView.undelegateEvents();
this.$('.profile-image-field').append(imageView.render().el);
imageView.delegateEvents();
if (this.showFullProfile()) {
_.each(this.options.sectionOneFieldViews, function (fieldView) {
fieldView.undelegateEvents();
view.$('.profile-section-one-fields').append(fieldView.render().el);
fieldView.delegateEvents();
});
_.each(this.options.sectionTwoFieldViews, function (fieldView) {
fieldView.undelegateEvents();
view.$('.profile-section-two-fields').append(fieldView.render().el);
fieldView.delegateEvents();
});
}
},
......
......@@ -225,6 +225,7 @@
value: this.modelValue(),
message: this.helpMessage
}));
this.delegateEvents();
return this;
},
......@@ -260,6 +261,7 @@
value: this.modelValue(),
message: this.helpMessage
}));
this.delegateEvents();
return this;
},
......@@ -308,7 +310,7 @@
selectOptions: this.options.options,
message: this.helpMessage
}));
this.delegateEvents();
this.updateValueInField();
if (this.editable === 'toggle') {
......@@ -415,7 +417,7 @@
value: value,
message: this.helpMessage
}));
this.delegateEvents();
this.title((this.modelValue() || this.mode === 'edit') ? this.options.title : this.indicators['plus'] + this.options.title);
if (this.editable === 'toggle') {
......@@ -491,6 +493,7 @@
linkHref: this.options.linkHref,
message: this.helpMessage
}));
this.delegateEvents();
return this;
},
......@@ -523,7 +526,8 @@
events: {
'click .u-field-upload-button': 'clickedUploadButton',
'click .u-field-remove-button': 'clickedRemoveButton'
'click .u-field-remove-button': 'clickedRemoveButton',
'click .upload-submit': 'clickedUploadButton'
},
initialize: function (options) {
......@@ -543,6 +547,7 @@
removeButtonIcon: _.result(this, 'iconRemove'),
removeButtonTitle: _.result(this, 'removeButtonTitle')
}));
this.delegateEvents();
this.updateButtonsVisibility();
this.watchForPageUnload();
return this;
......@@ -558,9 +563,9 @@
uploadButtonTitle: function () {
if (this.isShowingPlaceholder()) {
return _.result(this, 'titleAdd')
return _.result(this, 'titleAdd');
} else {
return _.result(this, 'titleEdit')
return _.result(this, 'titleEdit');
}
},
......@@ -569,7 +574,7 @@
},
isEditingAllowed: function () {
return true
return true;
},
isShowingPlaceholder: function () {
......@@ -594,7 +599,7 @@
}
},
clickedUploadButton: function (e, data) {
clickedUploadButton: function () {
$(this.uploadButtonSelector).fileupload({
url: this.options.imageUploadUrl,
type: 'POST',
......@@ -604,24 +609,22 @@
});
},
clickedRemoveButton: function (e, data) {
clickedRemoveButton: function () {
var view = this;
this.setCurrentStatus('removing');
this.setUploadButtonVisibility('none');
this.showRemovalInProgressMessage();
$.ajax({
$.ajax({
type: 'POST',
url: this.options.imageRemoveUrl,
success: function (data, status, xhr) {
view.imageChangeSucceeded();
},
error: function (xhr, status, error) {
view.showImageChangeFailedMessage(xhr.status, xhr.responseText);
}
url: this.options.imageRemoveUrl
}).done(function () {
view.imageChangeSucceeded();
}).fail(function (jqXHR) {
view.showImageChangeFailedMessage(jqXHR.status, jqXHR.responseText);
});
},
imageChangeSucceeded: function (e, data) {
imageChangeSucceeded: function () {
this.render();
},
......@@ -645,11 +648,19 @@
var humanReadableSize;
if (imageBytes < this.options.imageMinBytes) {
humanReadableSize = this.bytesToHumanReadable(this.options.imageMinBytes);
this.showErrorMessage(interpolate_text(gettext("Your image must be at least {size} in size."), {size: humanReadableSize}));
this.showErrorMessage(
interpolate_text(
gettext("Your image must be at least {size} in size."), {size: humanReadableSize}
)
);
return false;
} else if (imageBytes > this.options.imageMaxBytes) {
humanReadableSize = this.bytesToHumanReadable(this.options.imageMaxBytes);
this.showErrorMessage(interpolate_text(gettext("Your image must be smaller than {size} in size."), {size: humanReadableSize}));
this.showErrorMessage(
interpolate_text(
gettext("Your image must be smaller than {size} in size."), {size: humanReadableSize}
)
);
return false;
}
return true;
......@@ -675,27 +686,25 @@
return this.$('.image-wrapper').attr('data-status');
},
inProgress: function() {
var status = this.getCurrentStatus();
return _.isUndefined(status) ? false : true;
},
watchForPageUnload: function () {
$(window).on('beforeunload', this.onBeforeUnload);
},
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.");
return gettext(
"Upload is in progress. To avoid errors, stay on this page until the process is complete."
);
} else if (status === 'removing') {
return gettext("Removal is in progress. To avoid errors, stay on this page until the process is complete.");
return gettext(
"Removal is in progress. To avoid errors, stay on this page until the process is complete."
);
}
},
bytesToHumanReadable: function (size) {
var units = ['Bytes', 'KB', 'MB'];
var units = ['bytes', 'KB', 'MB'];
var i = 0;
while(size >= 1024) {
size /= 1024;
......
......@@ -6,14 +6,18 @@
var MessageBannerView = Backbone.View.extend({
initialize: function (options) {
initialize: function () {
this.template = _.template($('#message_banner-tpl').text());
},
render: function () {
this.$el.html(this.template({
message: this.message
}));
if (_.isUndefined(this.message) || _.isNull(this.message)) {
this.$el.html('');
} else {
this.$el.html(this.template({
message: this.message
}));
}
return this;
},
......@@ -23,10 +27,11 @@
},
hideMessage: function () {
this.$el.html('');
this.message = null;
this.render();
}
});
return MessageBannerView;
})
});
}).call(this, define || RequireJS.define);
......@@ -30,7 +30,6 @@
background: transparent !important;
border: none !important;
padding: 0;
-webkit-tap-highlight-color: transparent;
}
.u-field-image {
......@@ -43,16 +42,18 @@
.image-frame {
position: relative;
width: 120px;
height: 120px;
width: $profile-image-dimension;
height: $profile-image-dimension;
border-radius: ($baseline/4);
}
.u-field-upload-button {
width: 120px;
height: 120px;
width: $profile-image-dimension;
height: $profile-image-dimension;
position: absolute;
top: 0;
opacity: 0;
@include transition(all $tmg-f1 ease-in-out 0s);
i {
color: $white;
......@@ -61,13 +62,15 @@
.upload-button-icon, .upload-button-title {
text-align: center;
transform: translateY(45px);
transform: translateY(35px);
display: block;
color: $white;
margin-bottom: ($baseline/4);
line-height: 1.3em;
}
.upload-button-input {
width: 120px;
width: $profile-image-dimension;
height: 100%;
position: absolute;
top: 0;
......@@ -77,17 +80,24 @@
}
.u-field-remove-button {
width: 120px;
height: 20px;
width: $profile-image-dimension;
height: $baseline;
opacity: 0;
position: relative;
margin-top: 2px;
text-align: center;
&:focus, &:active {
box-shadow: none;
outline: 0;
}
}
&:hover {
.u-field-upload-button, .u-field-remove-button {
opacity: 1;
background-color: $shadow-d2;
border-radius: ($baseline/4);
}
}
}
......
......@@ -6,10 +6,11 @@
<span class="upload-button-title" aria-live="polite"><%= uploadButtonTitle %></span>
<input class="upload-button-input" type="file" name="<%= id %>"/>
</label>
<button class="upload-submit" type="button" hidden="true"><%= uploadButtonTitle %></button>
<button class="u-field-remove-button" type="button">
<span class="remove-button-icon" aria-hidden="true"><%= removeButtonIcon %></span>
<span class="remove-button-title" aria-live="polite"><%= removeButtonTitle %></span>
</button>
</div>
</div>
\ No newline at end of file
</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