Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
c2d83bd4
Commit
c2d83bd4
authored
Apr 10, 2015
by
muzaffaryousaf
Committed by
Andy Armstrong
Apr 17, 2015
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Bok_choy tests for upload/remove profile image.
TNL-1538
parent
ca1f2f77
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
584 additions
and
216 deletions
+584
-216
common/test/acceptance/pages/lms/learner_profile.py
+108
-0
common/test/acceptance/tests/lms/test_learner_profile.py
+172
-0
common/test/data/uploads/larger_image.jpg
+0
-0
common/test/data/uploads/list-icon-visited.png
+0
-0
lms/envs/bok_choy.py
+8
-0
lms/static/js/fixtures/student_profile/student_profile.html
+20
-0
lms/static/js/spec/student_profile/learner_profile_factory_spec.js
+1
-1
lms/static/js/spec/student_profile/learner_profile_fields_spec.js
+177
-162
lms/static/js/spec/student_profile/learner_profile_view_spec.js
+1
-1
lms/static/js/spec/views/message_banner_spec.js
+27
-0
lms/static/js/student_profile/views/learner_profile_factory.js
+1
-1
lms/static/js/student_profile/views/learner_profile_view.js
+0
-6
lms/static/js/views/fields.js
+37
-28
lms/static/js/views/message_banner.js
+11
-6
lms/static/sass/views/_learner-profile.scss
+19
-9
lms/templates/fields/field_image.underscore
+2
-2
No files found.
common/test/acceptance/pages/lms/learner_profile.py
View file @
c2d83bd4
...
...
@@ -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
common/test/acceptance/tests/lms/test_learner_profile.py
View file @
c2d83bd4
...
...
@@ -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
):
"""
...
...
common/test/data/uploads/larger_image.jpg
0 → 100644
View file @
c2d83bd4
1.02 MB
common/test/data/uploads/list-icon-visited.png
0 → 100644
View file @
c2d83bd4
99 Bytes
lms/envs/bok_choy.py
View file @
c2d83bd4
...
...
@@ -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
:
...
...
lms/static/js/fixtures/student_profile/student_profile.html
0 → 100644
View file @
c2d83bd4
<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>
lms/static/js/spec/student_profile/learner_profile_factory_spec.js
View file @
c2d83bd4
...
...
@@ -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'
);
...
...
lms/static/js/spec/student_profile/learner_profile_fields_spec.js
View file @
c2d83bd4
...
...
@@ -10,15 +10,20 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
describe
(
"edx.user.LearnerProfileFields"
,
function
()
{
var
createImageView
=
function
(
ownProfile
,
hasImage
,
imageMaxBytes
,
imageMinBytes
,
yearOfBirth
)
{
var
MOCK_YEAR_OF_BIRTH
=
1989
;
var
MOCK_IMAGE_MAX_BYTES
=
64
;
var
MOCK_IMAGE_MIN_BYTES
=
16
;
var
createImageView
=
function
(
options
)
{
var
yearOfBirth
=
_
.
isUndefined
(
options
.
yearOfBirth
)
?
MOCK_YEAR_OF_BIRTH
:
options
.
yearOfBirth
;
var
imageMaxBytes
=
_
.
isUndefined
(
options
.
imageMaxBytes
)
?
MOCK_IMAGE_MAX_BYTES
:
options
.
imageMaxBytes
;
var
imageMinBytes
=
_
.
isUndefined
(
options
.
imageMinBytes
)
?
MOCK_IMAGE_MIN_BYTES
:
options
.
imageMinBytes
;
var
imageData
=
{
image_url_large
:
'/media/profile-images/default.jpg'
,
has_image
:
hasImage
?
true
:
false
has_image
:
options
.
hasImage
?
true
:
false
};
yearOfBirth
=
_
.
isUndefined
(
yearOfBirth
)
?
1989
:
yearOfBirth
;
var
accountSettingsModel
=
new
UserAccountModel
();
accountSettingsModel
.
set
({
'profile_image'
:
imageData
});
accountSettingsModel
.
set
({
'year_of_birth'
:
yearOfBirth
});
...
...
@@ -30,15 +35,10 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
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'
,
editable
:
options
.
ownProfile
,
messageView
:
messageView
,
imageMaxBytes
:
imageMaxBytes
,
imageMinBytes
:
imageMinBytes
,
...
...
@@ -48,7 +48,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/student_profile/learner_profile'
);
TemplateHelpers
.
installTemplate
(
'templates/fields/field_image'
);
TemplateHelpers
.
installTemplate
(
"templates/fields/message_banner"
);
...
...
@@ -62,215 +62,230 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
);
};
it
(
"can upload profile image"
,
function
()
{
var
initializeUploader
=
function
(
view
)
{
view
.
$
(
'.upload-button-input'
).
fileupload
({
url
:
Helpers
.
IMAGE_UPLOAD_API_URL
,
type
:
'POST'
,
add
:
view
.
fileSelected
,
done
:
view
.
imageChangeSucceeded
,
fail
:
view
.
imageChangeFailed
});
};
var
imageView
=
createImageView
(
true
,
false
);
imageView
.
render
();
describe
(
"ProfileImageFieldView"
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
var
verifyImageUploadButtonMessage
=
function
(
view
,
inProgress
)
{
var
iconName
=
inProgress
?
'fa-spinner'
:
'fa-camera'
;
var
message
=
inProgress
?
view
.
titleUploading
:
view
.
uploadButtonTitle
();
expect
(
view
.
$
(
'.upload-button-icon i'
).
attr
(
'class'
)).
toContain
(
iconName
);
expect
(
view
.
$
(
'.upload-button-title'
).
text
().
trim
()).
toBe
(
message
);
};
var
imageName
=
'profile_image.jpg'
;
var
verifyImageRemoveButtonMessage
=
function
(
view
,
inProgress
)
{
var
iconName
=
inProgress
?
'fa-spinner'
:
'fa-remove'
;
var
message
=
inProgress
?
view
.
titleRemoving
:
view
.
removeButtonTitle
();
expect
(
view
.
$
(
'.remove-button-icon i'
).
attr
(
'class'
)).
toContain
(
iconName
);
expect
(
view
.
$
(
'.remove-button-title'
).
text
().
trim
()).
toBe
(
message
);
};
// 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
});
it
(
"can upload profile image"
,
function
()
{
// Remove button should not be present for default image
expect
(
imageView
.
$
(
'.u-field-remove-button'
).
css
(
'display'
)
===
'none'
).
toBeTruthy
();
var
imageView
=
createImageView
({
ownProfile
:
true
,
hasImage
:
false
});
imageView
.
render
();
// For default image, image title should be `Upload an image`
expect
(
imageView
.
$
(
'.upload-button-title'
).
text
().
trim
()).
toBe
(
imageView
.
titleAdd
)
;
var
requests
=
AjaxHelpers
.
requests
(
this
);
var
imageName
=
'profile_image.jpg'
;
// 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
)]});
initializeUploader
(
imageView
);
// Verify image upload progress mess
age
expect
(
imageView
.
$
(
'.upload-button-title'
).
text
().
trim
()).
toBe
(
imageView
.
titleUploading
);
// Remove button should not be present for default im
age
expect
(
imageView
.
$
(
'.u-field-remove-button'
).
css
(
'display'
)
===
'none'
).
toBeTruthy
(
);
// Verify if POST request received for image upload
AjaxHelpers
.
expectRequest
(
requests
,
'POST'
,
Helpers
.
IMAGE_UPLOAD_API_URL
,
new
FormData
()
);
// For default image, image title should be `Upload an image`
verifyImageUploadButtonMessage
(
imageView
,
false
);
// Send 204 NO CONTENT to confirm the image upload success
AjaxHelpers
.
respondWithNoContent
(
requests
);
// Add image to upload queue. Validate the image size and send POST request to upload image
imageView
.
$
(
'.upload-button-input'
).
fileupload
(
'add'
,
{
files
:
[
createFakeImageFile
(
60
)]}
);
// 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 image upload progress message
verifyImageUploadButtonMessage
(
imageView
,
true
);
// Verify uploaded image name
expect
(
imageView
.
$
(
'.image-frame'
).
attr
(
'src'
)).
toContain
(
imageName
);
// Verify if POST request received for image upload
AjaxHelpers
.
expectRequest
(
requests
,
'POST'
,
Helpers
.
IMAGE_UPLOAD_API_URL
,
new
FormData
()
);
// Remove button should be present after successful image upload
expect
(
imageView
.
$
(
'.u-field-remove-button'
).
css
(
'display'
)
!==
'none'
).
toBeTruthy
(
);
// Send 204 NO CONTENT to confirm the image upload success
AjaxHelpers
.
respondWithNoContent
(
requests
);
// After image upload, image title should be `Change image`
expect
(
imageView
.
$
(
'.upload-button-title'
).
text
().
trim
()).
toBe
(
imageView
.
titleEdit
);
});
// 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
);
it
(
"can remove profile image"
,
function
()
{
var
imageView
=
createImageView
(
true
,
true
);
imageView
.
render
();
// Verify uploaded image name
expect
(
imageView
.
$
(
'.image-frame'
).
attr
(
'src'
)).
toContain
(
imageName
);
var
requests
=
AjaxHelpers
.
requests
(
this
);
// Remove button should be present after successful image upload
expect
(
imageView
.
$
(
'.u-field-remove-button'
).
css
(
'display'
)
!==
'none'
).
toBeTruthy
();
imageView
.
$
(
'.u-field-remove-button'
).
click
();
// After image upload, image title should be `Change image`
verifyImageUploadButtonMessage
(
imageView
,
false
);
});
// Verify image remove progress message
expect
(
imageView
.
$
(
'.remove-button-title'
).
text
().
trim
()).
toBe
(
imageView
.
titleRemoving
);
it
(
"can remove profile image"
,
function
()
{
// Verify if POST request received for image remove
AjaxHelpers
.
expectRequest
(
requests
,
'POST'
,
Helpers
.
IMAGE_REMOVE_API_URL
,
null
);
var
imageView
=
createImageView
({
ownProfile
:
true
,
hasImage
:
false
});
imageView
.
render
(
);
// Send 204 NO CONTENT to confirm the image removal success
AjaxHelpers
.
respondWithNoContent
(
requests
);
var
requests
=
AjaxHelpers
.
requests
(
this
);
// 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
);
// Verify image remove title
verifyImageRemoveButtonMessage
(
imageView
,
false
);
// 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
();
it
(
"can't remove default profile image"
,
function
()
{
var
imageView
=
createImageView
(
true
,
false
);
imageView
.
render
();
// Verify image remove progress message
verifyImageRemoveButtonMessage
(
imageView
,
true
);
spyOn
(
imageView
,
'clickedRemoveButton'
);
// Verify if POST request received for image remove
AjaxHelpers
.
expectRequest
(
requests
,
'POST'
,
Helpers
.
IMAGE_REMOVE_API_URL
,
null
);
// Remove button should not be present for default image
expect
(
imageView
.
$
(
'.u-field-remove-button'
).
css
(
'display'
)
===
'none'
).
toBeTruthy
(
);
// Send 204 NO CONTENT to confirm the image removal success
AjaxHelpers
.
respondWithNoContent
(
requests
);
imageView
.
$
(
'.u-field-remove-button'
).
click
();
// Upon successful image removal, account settings model will be fetched to get 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 click handler should not be called
expect
(
imageView
.
clickedRemoveButton
).
not
.
toHaveBeenCalled
();
});
// Remove button should not be present for default image
expect
(
imageView
.
$
(
'.u-field-remove-button'
).
css
(
'display'
)
===
'none'
).
toBeTruthy
();
});
it
(
"can't upload image having size greater than max size"
,
function
()
{
var
imageView
=
createImageView
(
true
,
false
);
imageView
.
render
();
it
(
"can't remove default profile image"
,
function
()
{
// 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
var
imageView
=
createImageView
({
ownProfile
:
true
,
hasImage
:
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
();
});
// Add image to upload queue, this will validate the image size
imageView
.
$
(
'.upload-button-input'
).
fileupload
(
'add'
,
{
files
:
[
createFakeImageFile
(
70
)]});
it
(
"can't upload image having size greater than max size"
,
function
()
{
// Verify error message
expect
(
$
(
'.message-banner'
).
text
().
trim
()).
toBe
(
'Your image must be smaller than 64 Bytes in size.'
);
});
var
imageView
=
createImageView
({
ownProfile
:
true
,
hasImage
:
false
});
imageView
.
render
();
it
(
"can't upload image having size less than min size"
,
function
()
{
var
imageView
=
createImageView
(
true
,
false
);
imageView
.
render
();
initializeUploader
(
imageView
);
// 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.'
);
});
// Add image to upload queue, this will validate the image size
imageView
.
$
(
'.upload-button-input'
).
fileupload
(
'add'
,
{
files
:
[
createFakeImageFile
(
10
)]});
it
(
"can't upload image having size less than min size"
,
function
()
{
var
imageView
=
createImageView
({
ownProfile
:
true
,
hasImage
:
false
});
imageView
.
render
();
// Verify error message
expect
(
$
(
'.message-banner'
).
text
().
trim
()).
toBe
(
'Your image must be at least 16 Bytes in size.'
);
});
initializeUploader
(
imageView
);
it
(
"can't upload/remove image if parental consent required"
,
function
()
{
var
imageView
=
createImageView
(
true
,
false
,
64
,
16
,
''
);
imageView
.
render
();
// Add image to upload queue, this will validate the image size
imageView
.
$
(
'.upload-button-input'
).
fileupload
(
'add'
,
{
files
:
[
createFakeImageFile
(
10
)]});
spyOn
(
imageView
,
'clickedUploadButton'
);
spyOn
(
imageView
,
'clickedRemoveButton'
);
// Verify error message
expect
(
$
(
'.message-banner'
).
text
().
trim
()).
toBe
(
'Your image must be at least 16 bytes in size.'
);
});
expect
(
imageView
.
$
(
'.u-field-upload-button'
).
css
(
'display'
)
===
'none'
).
toBeTruthy
();
expect
(
imageView
.
$
(
'.u-field-remove-button'
).
css
(
'display'
)
===
'none'
).
toBeTruthy
();
it
(
"can't upload and remove image if parental consent required"
,
function
()
{
imageView
.
$
(
'.u-field-upload-button'
).
click
(
);
imageView
.
$
(
'.u-field-remove-button'
).
click
();
var
imageView
=
createImageView
({
ownProfile
:
true
,
hasImage
:
false
,
yearOfBirth
:
''
}
);
imageView
.
render
();
expect
(
imageView
.
clickedUploadButton
).
not
.
toHaveBeenCalled
();
expect
(
imageView
.
clickedRemoveButton
).
not
.
toHaveBeenCalled
();
});
spyOn
(
imageView
,
'clickedUploadButton'
);
spyOn
(
imageView
,
'clickedRemoveButton'
);
it
(
"can't upload image on others profile"
,
function
()
{
var
imageView
=
createImageView
(
false
);
imageView
.
render
();
expect
(
imageView
.
$
(
'.u-field-upload-button'
).
css
(
'display'
)
===
'none'
).
toBeTruthy
();
expect
(
imageView
.
$
(
'.u-field-remove-button'
).
css
(
'display'
)
===
'none'
).
toBeTruthy
();
spyOn
(
imageView
,
'clickedUploadButton'
);
spyOn
(
imageView
,
'clickedRemoveButton'
);
imageView
.
$
(
'.u-field-upload-button'
).
click
(
);
imageView
.
$
(
'.u-field-remove-button'
).
click
(
);
expect
(
imageView
.
$
(
'.u-field-upload-button'
).
css
(
'display'
)
===
'none'
).
toBeTruthy
();
expect
(
imageView
.
$
(
'.u-field-remove-button'
).
css
(
'display'
)
===
'none'
).
toBeTruthy
();
expect
(
imageView
.
clickedUploadButton
).
not
.
toHaveBeenCalled
();
expect
(
imageView
.
clickedRemoveButton
).
not
.
toHaveBeenCalled
();
});
imageView
.
$
(
'.u-field-upload-button'
).
click
();
imageView
.
$
(
'.u-field-remove-button'
).
click
();
it
(
"can't upload and remove image on others profile"
,
function
()
{
expect
(
imageView
.
clickedUploadButton
).
not
.
toHaveBeenCalled
();
expect
(
imageView
.
clickedRemoveButton
).
not
.
toHaveBeenCalled
();
});
var
imageView
=
createImageView
({
ownProfile
:
false
});
imageView
.
render
();
it
(
"shows message if we try to navigate away during image upload/remove"
,
function
()
{
var
imageView
=
createImageView
(
true
,
false
);
spyOn
(
imageView
,
'onBeforeUnload'
);
imageView
.
render
();
spyOn
(
imageView
,
'clickedUploadButton'
);
spyOn
(
imageView
,
'clickedRemoveButton'
);
// 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
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
();
});
// 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
)]});
it
(
"shows message if we try to navigate away during image upload/remove"
,
function
()
{
var
imageView
=
createImageView
({
ownProfile
:
true
,
hasImage
:
false
});
spyOn
(
imageView
,
'onBeforeUnload'
);
imageView
.
render
();
// Verify image upload progress message
expect
(
imageView
.
$
(
'.upload-button-title'
).
text
().
trim
()).
toBe
(
imageView
.
titleUploading
);
initializeUploader
(
imageView
);
$
(
window
).
trigger
(
'beforeunload'
);
// Add image to upload queue, this will validate image size and send POST request to upload image
imageView
.
$
(
'.upload-button-input'
).
fileupload
(
'add'
,
{
files
:
[
createFakeImageFile
(
60
)]});
expect
(
imageView
.
onBeforeUnload
).
toHaveBeenCalled
();
}
);
// Verify image upload progress message
verifyImageUploadButtonMessage
(
imageView
,
true
);
it
(
'renders message correctly'
,
function
()
{
var
messageSelector
=
'.message-banner'
;
var
messageView
=
new
MessageBannerView
({
el
:
$
(
messageSelector
)
$
(
window
).
trigger
(
'beforeunload'
);
expect
(
imageView
.
onBeforeUnload
).
toHaveBeenCalled
();
});
messageView
.
showMessage
(
'I am message view'
);
// Verify error message
expect
(
$
(
messageSelector
).
text
().
trim
()).
toBe
(
'I am message view'
);
it
(
"shows error message for HTTP 500"
,
function
()
{
var
imageView
=
createImageView
({
ownProfile
:
true
,
hasImage
:
false
});
imageView
.
render
();
var
requests
=
AjaxHelpers
.
requests
(
this
);
initializeUploader
(
imageView
);
messageView
.
hideMessage
();
expect
(
$
(
messageSelector
).
text
().
trim
()).
toBe
(
''
);
// Add image to upload queue. 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
verifyImageUploadButtonMessage
(
imageView
,
true
);
// Verify if POST request received for image upload
AjaxHelpers
.
expectRequest
(
requests
,
'POST'
,
Helpers
.
IMAGE_UPLOAD_API_URL
,
new
FormData
());
// Send HTTP 500
AjaxHelpers
.
respondWithError
(
requests
);
expect
(
$
(
'.message-banner'
).
text
().
trim
()).
toBe
(
imageView
.
errorMessage
);
});
});
});
});
lms/static/js/spec/student_profile/learner_profile_view_spec.js
View file @
c2d83bd4
...
...
@@ -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'
);
...
...
lms/static/js/spec/views/message_banner_spec.js
0 → 100644
View file @
c2d83bd4
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
(
''
);
});
});
});
lms/static/js/student_profile/views/learner_profile_factory.js
View file @
c2d83bd4
...
...
@@ -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
)
{
...
...
lms/static/js/student_profile/views/learner_profile_view.js
View file @
c2d83bd4
...
...
@@ -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
();
});
}
},
...
...
lms/static/js/views/fields.js
View file @
c2d83bd4
...
...
@@ -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
=
[
'
B
ytes'
,
'KB'
,
'MB'
];
var
units
=
[
'
b
ytes'
,
'KB'
,
'MB'
];
var
i
=
0
;
while
(
size
>=
1024
)
{
size
/=
1024
;
...
...
lms/static/js/views/message_banner.js
View file @
c2d83bd4
...
...
@@ -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
);
lms/static/sass/views/_learner-profile.scss
View file @
c2d83bd4
...
...
@@ -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
(
4
5px
);
transform
:
translateY
(
3
5px
);
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
);
}
}
}
...
...
lms/templates/fields/field_image.underscore
View file @
c2d83bd4
...
...
@@ -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>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment