Learner profile page.
TNL-1502
This commit is contained in:
committed by
Andy Armstrong
parent
229a37b983
commit
4f97cd7a04
@@ -202,3 +202,9 @@ class DashboardPage(PageObject):
|
||||
Click on `Account Settings` link.
|
||||
"""
|
||||
self.q(css='.dropdown-menu li a').first.click()
|
||||
|
||||
def click_my_profile_link(self):
|
||||
"""
|
||||
Click on `My Profile` link.
|
||||
"""
|
||||
self.q(css='.dropdown-menu li a').nth(1).click()
|
||||
|
||||
@@ -30,6 +30,40 @@ class FieldsMixin(object):
|
||||
"Field with id \"{0}\" is in DOM.".format(field_id)
|
||||
).fulfill()
|
||||
|
||||
def mode_for_field(self, field_id):
|
||||
"""
|
||||
Extract current field mode.
|
||||
|
||||
Returns:
|
||||
`placeholder`/`edit`/`display`
|
||||
"""
|
||||
self.wait_for_field(field_id)
|
||||
|
||||
query = self.q(css='.u-field-{}'.format(field_id))
|
||||
|
||||
if not query.present:
|
||||
return None
|
||||
|
||||
field_classes = query.attrs('class')[0].split()
|
||||
|
||||
if 'mode-placeholder' in field_classes:
|
||||
return 'placeholder'
|
||||
|
||||
if 'mode-display' in field_classes:
|
||||
return 'display'
|
||||
|
||||
if 'mode-edit' in field_classes:
|
||||
return 'edit'
|
||||
|
||||
def icon_for_field(self, field_id, icon_id):
|
||||
"""
|
||||
Check if field icon is present.
|
||||
"""
|
||||
self.wait_for_field(field_id)
|
||||
|
||||
query = self.q(css='.u-field-{} .u-field-icon'.format(field_id))
|
||||
return query.present and icon_id in query.attrs('class')[0].split()
|
||||
|
||||
def title_for_field(self, field_id):
|
||||
"""
|
||||
Return the title of a field.
|
||||
@@ -79,6 +113,23 @@ class FieldsMixin(object):
|
||||
"Indicator \"{0}\" is visible.".format(self.indicator_for_field(field_id))
|
||||
).fulfill()
|
||||
|
||||
def make_field_editable(self, field_id):
|
||||
"""
|
||||
Make a field editable.
|
||||
"""
|
||||
query = self.q(css='.u-field-{}'.format(field_id))
|
||||
|
||||
if not query.present:
|
||||
return None
|
||||
|
||||
field_classes = query.attrs('class')[0].split()
|
||||
|
||||
if 'mode-placeholder' in field_classes or 'mode-display' in field_classes:
|
||||
if field_id == 'bio':
|
||||
self.q(css='.u-field-bio > .wrapper-u-field').first.click()
|
||||
else:
|
||||
self.q(css='.u-field-{}'.format(field_id)).first.click()
|
||||
|
||||
def value_for_readonly_field(self, field_id):
|
||||
"""
|
||||
Return the value in a readonly field.
|
||||
@@ -104,19 +155,54 @@ class FieldsMixin(object):
|
||||
query.results[0].send_keys(u'\ue007') # Press Enter
|
||||
return query.attrs('value')[0]
|
||||
|
||||
def value_for_textarea_field(self, field_id, value=None):
|
||||
"""
|
||||
Get or set the value of a textarea field.
|
||||
"""
|
||||
self.wait_for_field(field_id)
|
||||
|
||||
self.make_field_editable(field_id)
|
||||
|
||||
query = self.q(css='.u-field-{} textarea'.format(field_id))
|
||||
if not query.present:
|
||||
return None
|
||||
|
||||
if value is not None:
|
||||
query.fill(value)
|
||||
query.results[0].send_keys(u'\ue004') # Focus Out using TAB
|
||||
|
||||
if self.mode_for_field(field_id) == 'edit':
|
||||
return query.text[0]
|
||||
else:
|
||||
return self.get_non_editable_mode_value(field_id)
|
||||
|
||||
def get_non_editable_mode_value(self, field_id):
|
||||
"""
|
||||
Return value of field in `display` or `placeholder` mode.
|
||||
"""
|
||||
self.wait_for_field(field_id)
|
||||
|
||||
return self.q(css='.u-field-{} .u-field-value'.format(field_id)).text[0]
|
||||
|
||||
def value_for_dropdown_field(self, field_id, value=None):
|
||||
"""
|
||||
Get or set the value in a dropdown field.
|
||||
"""
|
||||
self.wait_for_field(field_id)
|
||||
|
||||
self.make_field_editable(field_id)
|
||||
|
||||
query = self.q(css='.u-field-{} select'.format(field_id))
|
||||
if not query.present:
|
||||
return None
|
||||
|
||||
if value is not None:
|
||||
select_option_by_text(query, value)
|
||||
return get_selected_option_text(query)
|
||||
|
||||
if self.mode_for_field(field_id) == 'edit':
|
||||
return get_selected_option_text(query)
|
||||
else:
|
||||
return self.get_non_editable_mode_value(field_id)
|
||||
|
||||
def link_title_for_link_field(self, field_id):
|
||||
"""
|
||||
|
||||
151
common/test/acceptance/pages/lms/learner_profile.py
Normal file
151
common/test/acceptance/pages/lms/learner_profile.py
Normal file
@@ -0,0 +1,151 @@
|
||||
"""
|
||||
Bok-Choy PageObject class for learner profile page.
|
||||
"""
|
||||
from . import BASE_URL
|
||||
from bok_choy.page_object import PageObject
|
||||
from .fields import FieldsMixin
|
||||
from bok_choy.promise import EmptyPromise
|
||||
|
||||
|
||||
PROFILE_VISIBILITY_SELECTOR = '#u-field-select-account_privacy option[value="{}"]'
|
||||
FIELD_ICONS = {
|
||||
'country': 'fa-map-marker',
|
||||
'language_proficiencies': 'fa-comment',
|
||||
}
|
||||
|
||||
|
||||
class LearnerProfilePage(FieldsMixin, PageObject):
|
||||
"""
|
||||
PageObject methods for Learning Profile Page.
|
||||
"""
|
||||
|
||||
def __init__(self, browser, username):
|
||||
"""
|
||||
Initialize the page.
|
||||
|
||||
Arguments:
|
||||
browser (Browser): The browser instance.
|
||||
username (str): Profile username.
|
||||
"""
|
||||
super(LearnerProfilePage, self).__init__(browser)
|
||||
self.username = username
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""
|
||||
Construct a URL to the page.
|
||||
"""
|
||||
return BASE_URL + "/u/" + self.username
|
||||
|
||||
def is_browser_on_page(self):
|
||||
"""
|
||||
Check if browser is showing correct page.
|
||||
"""
|
||||
return 'Learner Profile' in self.browser.title
|
||||
|
||||
@property
|
||||
def privacy(self):
|
||||
"""
|
||||
Get user profile privacy.
|
||||
|
||||
Returns:
|
||||
'all_users' or 'private'
|
||||
"""
|
||||
return 'all_users' if self.q(css=PROFILE_VISIBILITY_SELECTOR.format('all_users')).selected else 'private'
|
||||
|
||||
@privacy.setter
|
||||
def privacy(self, privacy):
|
||||
"""
|
||||
Set user profile privacy.
|
||||
|
||||
Arguments:
|
||||
privacy (str): 'all_users' or 'private'
|
||||
"""
|
||||
self.wait_for_element_visibility('select#u-field-select-account_privacy', 'Privacy dropdown is visiblie')
|
||||
|
||||
if privacy != self.privacy:
|
||||
self.q(css=PROFILE_VISIBILITY_SELECTOR.format(privacy)).first.click()
|
||||
EmptyPromise(lambda: privacy == self.privacy, 'Privacy is set to {}'.format(privacy)).fulfill()
|
||||
self.wait_for_ajax()
|
||||
|
||||
if privacy == 'all_users':
|
||||
self.wait_for_public_fields()
|
||||
|
||||
def field_is_visible(self, field_id):
|
||||
"""
|
||||
Check if a field with id set to `field_id` is shown.
|
||||
|
||||
Arguments:
|
||||
field_id (str): field id
|
||||
|
||||
Returns:
|
||||
True/False
|
||||
"""
|
||||
self.wait_for_ajax()
|
||||
return self.q(css='.u-field-{}'.format(field_id)).visible
|
||||
|
||||
def field_is_editable(self, field_id):
|
||||
"""
|
||||
Check if a field with id set to `field_id` is editable.
|
||||
|
||||
Arguments:
|
||||
field_id (str): field id
|
||||
|
||||
Returns:
|
||||
True/False
|
||||
"""
|
||||
self.wait_for_field(field_id)
|
||||
self.make_field_editable(field_id)
|
||||
return self.mode_for_field(field_id) == 'edit'
|
||||
|
||||
@property
|
||||
def visible_fields(self):
|
||||
"""
|
||||
Return list of visible fields.
|
||||
"""
|
||||
self.wait_for_field('username')
|
||||
|
||||
fields = ['username', 'country', 'language_proficiencies', 'bio']
|
||||
return [field for field in fields if self.field_is_visible(field)]
|
||||
|
||||
@property
|
||||
def editable_fields(self):
|
||||
"""
|
||||
Return list of editable fields currently shown on page.
|
||||
"""
|
||||
self.wait_for_ajax()
|
||||
self.wait_for_element_visibility('.u-field-username', 'username is not visible')
|
||||
|
||||
fields = ['country', 'language_proficiencies', 'bio']
|
||||
return [field for field in fields if self.field_is_editable(field)]
|
||||
|
||||
@property
|
||||
def privacy_field_visible(self):
|
||||
"""
|
||||
Check if profile visibility selector is shown or not.
|
||||
|
||||
Returns:
|
||||
True/False
|
||||
"""
|
||||
self.wait_for_ajax()
|
||||
return self.q(css='#u-field-select-account_privacy').visible
|
||||
|
||||
def field_icon_present(self, field_id):
|
||||
"""
|
||||
Check if an icon is present for a field. Only dropdown fields have icons.
|
||||
|
||||
Arguments:
|
||||
field_id (str): field id
|
||||
|
||||
Returns:
|
||||
True/False
|
||||
"""
|
||||
return self.icon_for_field(field_id, FIELD_ICONS[field_id])
|
||||
|
||||
def wait_for_public_fields(self):
|
||||
"""
|
||||
Wait for `country`, `language` and `bio` fields to be visible.
|
||||
"""
|
||||
EmptyPromise(lambda: self.field_is_visible('country'), 'Country field is visible').fulfill()
|
||||
EmptyPromise(lambda: self.field_is_visible('language_proficiencies'), 'Language field is visible').fulfill()
|
||||
EmptyPromise(lambda: self.field_is_visible('bio'), 'About Me field is visible').fulfill()
|
||||
307
common/test/acceptance/tests/lms/test_learner_profile.py
Normal file
307
common/test/acceptance/tests/lms/test_learner_profile.py
Normal file
@@ -0,0 +1,307 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
End-to-end tests for Student's Profile Page.
|
||||
"""
|
||||
|
||||
from ...pages.lms.account_settings import AccountSettingsPage
|
||||
from ...pages.lms.auto_auth import AutoAuthPage
|
||||
from ...pages.lms.learner_profile import LearnerProfilePage
|
||||
from ...pages.lms.dashboard import DashboardPage
|
||||
|
||||
from bok_choy.web_app_test import WebAppTest
|
||||
|
||||
|
||||
class LearnerProfilePageTest(WebAppTest):
|
||||
"""
|
||||
Tests that verify Student's Profile Page.
|
||||
"""
|
||||
|
||||
USER_1_NAME = 'user1'
|
||||
USER_1_EMAIL = 'user1@edx.org'
|
||||
USER_2_NAME = 'user2'
|
||||
USER_2_EMAIL = 'user2@edx.org'
|
||||
|
||||
MY_USER = 1
|
||||
OTHER_USER = 2
|
||||
|
||||
PRIVACY_PUBLIC = 'all_users'
|
||||
PRIVACY_PRIVATE = 'private'
|
||||
|
||||
PUBLIC_PROFILE_FIELDS = ['username', 'country', 'language_proficiencies', 'bio']
|
||||
PRIVATE_PROFILE_FIELDS = ['username']
|
||||
|
||||
PUBLIC_PROFILE_EDITABLE_FIELDS = ['country', 'language_proficiencies', 'bio']
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Initialize pages.
|
||||
"""
|
||||
super(LearnerProfilePageTest, self).setUp()
|
||||
|
||||
self.account_settings_page = AccountSettingsPage(self.browser)
|
||||
self.dashboard_page = DashboardPage(self.browser)
|
||||
|
||||
self.my_auto_auth_page = AutoAuthPage(self.browser, username=self.USER_1_NAME, email=self.USER_1_EMAIL).visit()
|
||||
self.my_profile_page = LearnerProfilePage(self.browser, self.USER_1_NAME)
|
||||
|
||||
self.other_auto_auth_page = AutoAuthPage(
|
||||
self.browser,
|
||||
username=self.USER_2_NAME,
|
||||
email=self.USER_2_EMAIL
|
||||
).visit()
|
||||
|
||||
self.other_profile_page = LearnerProfilePage(self.browser, self.USER_2_NAME)
|
||||
|
||||
def authenticate_as_user(self, user):
|
||||
"""
|
||||
Auto authenticate a user.
|
||||
"""
|
||||
if user == self.MY_USER:
|
||||
self.my_auto_auth_page.visit()
|
||||
elif user == self.OTHER_USER:
|
||||
self.other_auto_auth_page.visit()
|
||||
|
||||
def set_pubilc_profile_fields_data(self, profile_page):
|
||||
"""
|
||||
Fill in the public profile fields of a user.
|
||||
"""
|
||||
profile_page.value_for_dropdown_field('language_proficiencies', 'English')
|
||||
profile_page.value_for_dropdown_field('country', 'United Kingdom')
|
||||
profile_page.value_for_textarea_field('bio', 'Nothing Special')
|
||||
|
||||
def visit_my_profile_page(self, user, privacy=None):
|
||||
"""
|
||||
Visits a users profile page.
|
||||
"""
|
||||
self.authenticate_as_user(user)
|
||||
self.my_profile_page.visit()
|
||||
self.my_profile_page.wait_for_page()
|
||||
|
||||
if user is self.MY_USER and privacy is not None:
|
||||
self.my_profile_page.privacy = privacy
|
||||
|
||||
if privacy == self.PRIVACY_PUBLIC:
|
||||
self.set_pubilc_profile_fields_data(self.my_profile_page)
|
||||
|
||||
def visit_other_profile_page(self, user, privacy=None):
|
||||
"""
|
||||
Visits a users profile page.
|
||||
"""
|
||||
self.authenticate_as_user(user)
|
||||
self.other_profile_page.visit()
|
||||
self.other_profile_page.wait_for_page()
|
||||
|
||||
if user is self.OTHER_USER and privacy is not None:
|
||||
self.account_settings_page.visit()
|
||||
self.account_settings_page.wait_for_page()
|
||||
self.assertEqual(self.account_settings_page.value_for_dropdown_field('year_of_birth', '1980'), '1980')
|
||||
|
||||
self.other_profile_page.visit()
|
||||
self.other_profile_page.wait_for_page()
|
||||
self.other_profile_page.privacy = privacy
|
||||
|
||||
if privacy == self.PRIVACY_PUBLIC:
|
||||
self.set_pubilc_profile_fields_data(self.other_profile_page)
|
||||
|
||||
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.
|
||||
|
||||
Given that I am a registered user.
|
||||
When I go to Dashboard page.
|
||||
And I click on username dropdown.
|
||||
Then I see My Profile link in the dropdown menu.
|
||||
When I click on My Profile link.
|
||||
Then I will be navigated to My Profile page.
|
||||
"""
|
||||
self.dashboard_page.visit()
|
||||
self.dashboard_page.click_username_dropdown()
|
||||
self.assertTrue('My Profile' in self.dashboard_page.username_dropdown_link_text)
|
||||
self.dashboard_page.click_my_profile_link()
|
||||
self.my_profile_page.wait_for_page()
|
||||
|
||||
def test_fields_on_my_private_profile(self):
|
||||
"""
|
||||
Scenario: Verify that desired fields are shown when looking at her own private profile.
|
||||
|
||||
Given that I am a registered user.
|
||||
And I visit My Profile page.
|
||||
And I set the profile visibility to private.
|
||||
And I reload the page.
|
||||
Then I should see the profile visibility selector dropdown.
|
||||
Then I see some of the profile fields are shown.
|
||||
"""
|
||||
self.visit_my_profile_page(self.MY_USER, privacy=self.PRIVACY_PRIVATE)
|
||||
|
||||
self.assertTrue(self.my_profile_page.privacy_field_visible)
|
||||
self.assertEqual(self.my_profile_page.visible_fields, self.PRIVATE_PROFILE_FIELDS)
|
||||
|
||||
def test_fields_on_my_public_profile(self):
|
||||
"""
|
||||
Scenario: Verify that desired fields are shown when looking at her own public profile.
|
||||
|
||||
Given that I am a registered user.
|
||||
And I visit My Profile page.
|
||||
And I set the profile visibility to public.
|
||||
And I reload the page.
|
||||
Then I should see the profile visibility selector dropdown.
|
||||
Then I see all the profile fields are shown.
|
||||
And `location`, `language` and `about me` fields are editable.
|
||||
"""
|
||||
self.visit_my_profile_page(self.MY_USER, privacy=self.PRIVACY_PUBLIC)
|
||||
|
||||
self.assertTrue(self.my_profile_page.privacy_field_visible)
|
||||
self.assertEqual(self.my_profile_page.visible_fields, self.PUBLIC_PROFILE_FIELDS)
|
||||
|
||||
self.assertEqual(self.my_profile_page.editable_fields, self.PUBLIC_PROFILE_EDITABLE_FIELDS)
|
||||
|
||||
def test_fields_on_others_private_profile(self):
|
||||
"""
|
||||
Scenario: Verify that desired fields are shown when looking at her own private profile.
|
||||
|
||||
Given that I am a registered user.
|
||||
And I visit others private profile page.
|
||||
Then I shouldn't see the profile visibility selector dropdown.
|
||||
Then I see some of the profile fields are shown.
|
||||
"""
|
||||
self.visit_other_profile_page(self.OTHER_USER, privacy=self.PRIVACY_PRIVATE)
|
||||
self.visit_other_profile_page(self.MY_USER)
|
||||
|
||||
self.assertFalse(self.other_profile_page.privacy_field_visible)
|
||||
self.assertEqual(self.other_profile_page.visible_fields, self.PRIVATE_PROFILE_FIELDS)
|
||||
|
||||
def test_fields_on_others_public_profile(self):
|
||||
"""
|
||||
Scenario: Verify that desired fields are shown when looking at her own public profile.
|
||||
|
||||
Given that I am a registered user.
|
||||
And I visit others public profile page.
|
||||
Then I shouldn't see the profile visibility selector dropdown.
|
||||
Then all the profile fields are shown.
|
||||
Then I shouldn't see the profile visibility selector dropdown.
|
||||
Also `location`, `language` and `about me` fields are not editable.
|
||||
"""
|
||||
self.visit_other_profile_page(self.OTHER_USER, privacy=self.PRIVACY_PUBLIC)
|
||||
self.visit_other_profile_page(self.MY_USER)
|
||||
|
||||
self.other_profile_page.wait_for_public_fields()
|
||||
self.assertFalse(self.other_profile_page.privacy_field_visible)
|
||||
|
||||
fields_to_check = self.PUBLIC_PROFILE_FIELDS
|
||||
self.assertEqual(self.other_profile_page.visible_fields, fields_to_check)
|
||||
|
||||
self.assertEqual(self.my_profile_page.editable_fields, [])
|
||||
|
||||
def _test_dropdown_field(self, field_id, new_value, displayed_value, mode):
|
||||
"""
|
||||
Test behaviour of a dropdown field.
|
||||
"""
|
||||
self.visit_my_profile_page(self.MY_USER, privacy=self.PRIVACY_PUBLIC)
|
||||
|
||||
self.my_profile_page.value_for_dropdown_field(field_id, new_value)
|
||||
self.assertEqual(self.my_profile_page.get_non_editable_mode_value(field_id), displayed_value)
|
||||
self.assertTrue(self.my_profile_page.mode_for_field(field_id), mode)
|
||||
|
||||
self.browser.refresh()
|
||||
self.my_profile_page.wait_for_page()
|
||||
|
||||
self.assertEqual(self.my_profile_page.get_non_editable_mode_value(field_id), displayed_value)
|
||||
self.assertTrue(self.my_profile_page.mode_for_field(field_id), mode)
|
||||
|
||||
def _test_textarea_field(self, field_id, new_value, displayed_value, mode):
|
||||
"""
|
||||
Test behaviour of a textarea field.
|
||||
"""
|
||||
self.visit_my_profile_page(self.MY_USER, privacy=self.PRIVACY_PUBLIC)
|
||||
|
||||
self.my_profile_page.value_for_textarea_field(field_id, new_value)
|
||||
self.assertEqual(self.my_profile_page.get_non_editable_mode_value(field_id), displayed_value)
|
||||
self.assertTrue(self.my_profile_page.mode_for_field(field_id), mode)
|
||||
|
||||
self.browser.refresh()
|
||||
self.my_profile_page.wait_for_page()
|
||||
|
||||
self.assertEqual(self.my_profile_page.get_non_editable_mode_value(field_id), displayed_value)
|
||||
self.assertTrue(self.my_profile_page.mode_for_field(field_id), mode)
|
||||
|
||||
def test_country_field(self):
|
||||
"""
|
||||
Test behaviour of `Country` field.
|
||||
|
||||
Given that I am a registered user.
|
||||
And I visit My Profile page.
|
||||
And I set the profile visibility to public and set default values for public fields.
|
||||
Then I set country value to `Pakistan`.
|
||||
Then displayed country should be `Pakistan` and country field mode should be `display`
|
||||
And I reload the page.
|
||||
Then displayed country should be `Pakistan` and country field mode should be `display`
|
||||
And I make `country` field editable
|
||||
Then `country` field mode should be `edit`
|
||||
And `country` field icon should be visible.
|
||||
"""
|
||||
self._test_dropdown_field('country', 'Pakistan', 'Pakistan', 'display')
|
||||
|
||||
self.my_profile_page.make_field_editable('country')
|
||||
self.assertTrue(self.my_profile_page.mode_for_field('country'), 'edit')
|
||||
|
||||
self.assertTrue(self.my_profile_page.field_icon_present('country'))
|
||||
|
||||
def test_language_field(self):
|
||||
"""
|
||||
Test behaviour of `Language` field.
|
||||
|
||||
Given that I am a registered user.
|
||||
And I visit My Profile page.
|
||||
And I set the profile visibility to public and set default values for public fields.
|
||||
Then I set language value to `Urdu`.
|
||||
Then displayed language should be `Urdu` and language field mode should be `display`
|
||||
And I reload the page.
|
||||
Then displayed language should be `Urdu` and language field mode should be `display`
|
||||
Then I set empty value for language.
|
||||
Then displayed language should be `Add language` and language field mode should be `placeholder`
|
||||
And I reload the page.
|
||||
Then displayed language should be `Add language` and language field mode should be `placeholder`
|
||||
And I make `language` field editable
|
||||
Then `language` field mode should be `edit`
|
||||
And `language` field icon should be visible.
|
||||
"""
|
||||
self._test_dropdown_field('language_proficiencies', 'Urdu', 'Urdu', 'display')
|
||||
self._test_dropdown_field('language_proficiencies', '', 'Add language', 'placeholder')
|
||||
|
||||
self.my_profile_page.make_field_editable('language_proficiencies')
|
||||
self.assertTrue(self.my_profile_page.mode_for_field('language_proficiencies'), 'edit')
|
||||
|
||||
self.assertTrue(self.my_profile_page.field_icon_present('language_proficiencies'))
|
||||
|
||||
def test_about_me_field(self):
|
||||
"""
|
||||
Test behaviour of `About Me` field.
|
||||
|
||||
Given that I am a registered user.
|
||||
And I visit My Profile page.
|
||||
And I set the profile visibility to public and set default values for public fields.
|
||||
Then I set about me value to `Eat Sleep Code`.
|
||||
Then displayed about me should be `Eat Sleep Code` and about me field mode should be `display`
|
||||
And I reload the page.
|
||||
Then displayed about me should be `Eat Sleep Code` and about me field mode should be `display`
|
||||
Then I set empty value for about me.
|
||||
Then displayed about me should be `Tell other edX learners a little about yourself: where you live,
|
||||
what your interests are, why you're taking courses on edX, or what you hope to learn.` and about me
|
||||
field mode should be `placeholder`
|
||||
And I reload the page.
|
||||
Then displayed about me should be `Tell other edX learners a little about yourself: where you live,
|
||||
what your interests are, why you're taking courses on edX, or what you hope to learn.` and about me
|
||||
field mode should be `placeholder`
|
||||
And I make `about me` field editable
|
||||
Then `about me` field mode should be `edit`
|
||||
"""
|
||||
placeholder_value = (
|
||||
"Tell other edX learners a little about yourself: where you live, what your interests are, "
|
||||
"why you're taking courses on edX, or what you hope to learn."
|
||||
)
|
||||
|
||||
self._test_textarea_field('bio', 'Eat Sleep Code', 'Eat Sleep Code', 'display')
|
||||
self._test_textarea_field('bio', '', placeholder_value, 'placeholder')
|
||||
|
||||
self.my_profile_page.make_field_editable('bio')
|
||||
self.assertTrue(self.my_profile_page.mode_for_field('bio'), 'edit')
|
||||
0
lms/djangoapps/student_profile/__init__.py
Normal file
0
lms/djangoapps/student_profile/__init__.py
Normal file
0
lms/djangoapps/student_profile/test/__init__.py
Normal file
0
lms/djangoapps/student_profile/test/__init__.py
Normal file
69
lms/djangoapps/student_profile/test/test_views.py
Normal file
69
lms/djangoapps/student_profile/test/test_views.py
Normal file
@@ -0,0 +1,69 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Tests for student profile views. """
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
|
||||
from util.testing import UrlResetMixin
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
from student_profile.views import learner_profile_context
|
||||
|
||||
|
||||
class LearnerProfileViewTest(UrlResetMixin, TestCase):
|
||||
""" Tests for the student profile view. """
|
||||
|
||||
USERNAME = "username"
|
||||
PASSWORD = "password"
|
||||
CONTEXT_DATA = [
|
||||
'default_public_account_fields',
|
||||
'accounts_api_url',
|
||||
'preferences_api_url',
|
||||
'account_settings_page_url',
|
||||
'has_preferences_access',
|
||||
'own_profile',
|
||||
'country_options',
|
||||
'language_options',
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(LearnerProfileViewTest, self).setUp()
|
||||
self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
|
||||
self.client.login(username=self.USERNAME, password=self.PASSWORD)
|
||||
|
||||
def test_context(self):
|
||||
"""
|
||||
Verify learner profile page context data.
|
||||
"""
|
||||
context = learner_profile_context(self.user.username, self.USERNAME, self.user.is_staff)
|
||||
|
||||
self.assertEqual(
|
||||
context['data']['default_public_account_fields'],
|
||||
settings.ACCOUNT_VISIBILITY_CONFIGURATION['public_fields']
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
context['data']['accounts_api_url'],
|
||||
reverse("accounts_api", kwargs={'username': self.user.username})
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
context['data']['preferences_api_url'],
|
||||
reverse('preferences_api', kwargs={'username': self.user.username})
|
||||
)
|
||||
|
||||
self.assertEqual(context['data']['account_settings_page_url'], reverse('account_settings'))
|
||||
|
||||
for attribute in self.CONTEXT_DATA:
|
||||
self.assertIn(attribute, context['data'])
|
||||
|
||||
def test_view(self):
|
||||
"""
|
||||
Verify learner profile page view.
|
||||
"""
|
||||
profile_path = reverse('learner_profile', kwargs={'username': self.USERNAME})
|
||||
response = self.client.get(path=profile_path)
|
||||
|
||||
for attribute in self.CONTEXT_DATA:
|
||||
self.assertIn(attribute, response.content)
|
||||
71
lms/djangoapps/student_profile/views.py
Normal file
71
lms/djangoapps/student_profile/views.py
Normal file
@@ -0,0 +1,71 @@
|
||||
""" Views for a student's profile information. """
|
||||
|
||||
from django.conf import settings
|
||||
from django_countries import countries
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.views.decorators.http import require_http_methods
|
||||
|
||||
from edxmako.shortcuts import render_to_response
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(['GET'])
|
||||
def learner_profile(request, username):
|
||||
"""
|
||||
Render the students profile page.
|
||||
|
||||
Args:
|
||||
request (HttpRequest)
|
||||
username (str): username of user whose profile is requested.
|
||||
|
||||
Returns:
|
||||
HttpResponse: 200 if the page was sent successfully
|
||||
HttpResponse: 302 if not logged in (redirect to login page)
|
||||
HttpResponse: 405 if using an unsupported HTTP method
|
||||
|
||||
Example usage:
|
||||
GET /account/profile
|
||||
"""
|
||||
return render_to_response(
|
||||
'student_profile/learner_profile.html',
|
||||
learner_profile_context(request.user.username, username, request.user.is_staff)
|
||||
)
|
||||
|
||||
|
||||
def learner_profile_context(logged_in_username, profile_username, user_is_staff):
|
||||
"""
|
||||
Context for the learner profile page.
|
||||
|
||||
Args:
|
||||
logged_in_username (str): Username of user logged In user.
|
||||
profile_username (str): username of user whose profile is requested.
|
||||
user_is_staff (bool): Logged In user has staff access.
|
||||
|
||||
Returns:
|
||||
dict
|
||||
"""
|
||||
language_options = [language for language in settings.ALL_LANGUAGES]
|
||||
|
||||
country_options = [
|
||||
(country_code, unicode(country_name))
|
||||
for country_code, country_name in sorted(
|
||||
countries.countries, key=lambda(__, name): unicode(name)
|
||||
)
|
||||
]
|
||||
|
||||
context = {
|
||||
'data': {
|
||||
'default_public_account_fields': settings.ACCOUNT_VISIBILITY_CONFIGURATION['public_fields'],
|
||||
'accounts_api_url': reverse("accounts_api", kwargs={'username': profile_username}),
|
||||
'preferences_api_url': reverse('preferences_api', kwargs={'username': profile_username}),
|
||||
'account_settings_page_url': reverse('account_settings'),
|
||||
'has_preferences_access': (logged_in_username == profile_username or user_is_staff),
|
||||
'own_profile': (logged_in_username == profile_username),
|
||||
'country_options': country_options,
|
||||
'language_options': language_options,
|
||||
}
|
||||
}
|
||||
|
||||
return context
|
||||
@@ -90,6 +90,9 @@
|
||||
'js/student_account/views/RegisterView': 'js/student_account/views/RegisterView',
|
||||
'js/student_account/views/AccessView': 'js/student_account/views/AccessView',
|
||||
'js/student_profile/profile': 'js/student_profile/profile',
|
||||
'js/student_profile/views/learner_profile_fields': 'js/student_profile/views/learner_profile_fields',
|
||||
'js/student_profile/views/learner_profile_factory': 'js/student_profile/views/learner_profile_factory',
|
||||
'js/student_profile/views/learner_profile_view': 'js/student_profile/views/learner_profile_view',
|
||||
|
||||
// edxnotes
|
||||
'annotator_1.2.9': 'xmodule_js/common_static/js/vendor/edxnotes/annotator-full.min'
|
||||
@@ -593,6 +596,8 @@
|
||||
'lms/include/js/spec/student_account/account_settings_view_spec.js',
|
||||
'lms/include/js/spec/student_profile/profile_spec.js',
|
||||
'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/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',
|
||||
|
||||
@@ -132,7 +132,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
expect(sectionsData[0].fields.length).toBe(5);
|
||||
|
||||
var textFields = [sectionsData[0].fields[1], sectionsData[0].fields[2]];
|
||||
for (var i = 0; i < textFields ; i++) {
|
||||
for (var i = 0; i < textFields.length ; i++) {
|
||||
|
||||
var view = textFields[i].view;
|
||||
FieldViewsSpecHelpers.verifyTextField(view, {
|
||||
@@ -154,9 +154,9 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
title: view.options.title,
|
||||
valueAttribute: view.options.valueAttribute,
|
||||
helpMessage: '',
|
||||
validValue: Helpers.FIELD_OPTIONS[0][0],
|
||||
invalidValue1: Helpers.FIELD_OPTIONS[1][0],
|
||||
invalidValue2: Helpers.FIELD_OPTIONS[2][0],
|
||||
validValue: Helpers.FIELD_OPTIONS[1][0],
|
||||
invalidValue1: Helpers.FIELD_OPTIONS[2][0],
|
||||
invalidValue2: Helpers.FIELD_OPTIONS[3][0],
|
||||
validationError: "Nope, this will not do!"
|
||||
}, requests);
|
||||
}
|
||||
|
||||
@@ -9,11 +9,13 @@ define(['underscore'], function(_) {
|
||||
name: 'Student',
|
||||
email: 'student@edx.org',
|
||||
|
||||
level_of_education: '1',
|
||||
gender: '2',
|
||||
year_of_birth: '3',
|
||||
country: '1',
|
||||
language: '2'
|
||||
level_of_education: '0',
|
||||
gender: '0',
|
||||
year_of_birth: '0',
|
||||
country: '0',
|
||||
language: '0',
|
||||
bio: "About the student",
|
||||
language_proficiencies: [{code: '1'}]
|
||||
};
|
||||
|
||||
var USER_PREFERENCES_DATA = {
|
||||
@@ -21,6 +23,7 @@ define(['underscore'], function(_) {
|
||||
};
|
||||
|
||||
var FIELD_OPTIONS = [
|
||||
['0', 'Option 0'],
|
||||
['1', 'Option 1'],
|
||||
['2', 'Option 2'],
|
||||
['3', 'Option 3'],
|
||||
@@ -28,9 +31,9 @@ define(['underscore'], function(_) {
|
||||
|
||||
var expectLoadingIndicatorIsVisible = function (view, visible) {
|
||||
if (visible) {
|
||||
expect(view.$('.ui-loading-indicator')).not.toHaveClass('is-hidden');
|
||||
expect($('.ui-loading-indicator')).not.toHaveClass('is-hidden');
|
||||
} else {
|
||||
expect(view.$('.ui-loading-indicator')).toHaveClass('is-hidden');
|
||||
expect($('.ui-loading-indicator')).toHaveClass('is-hidden');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -95,6 +98,6 @@ define(['underscore'], function(_) {
|
||||
expectLoadingErrorIsVisible: expectLoadingErrorIsVisible,
|
||||
expectElementContainsField: expectElementContainsField,
|
||||
expectSettingsSectionsButNotFieldsToBeRendered: expectSettingsSectionsButNotFieldsToBeRendered,
|
||||
expectSettingsSectionsAndFieldsToBeRendered: expectSettingsSectionsAndFieldsToBeRendered
|
||||
expectSettingsSectionsAndFieldsToBeRendered: expectSettingsSectionsAndFieldsToBeRendered,
|
||||
};
|
||||
});
|
||||
|
||||
100
lms/static/js/spec/student_profile/helpers.js
Normal file
100
lms/static/js/spec/student_profile/helpers.js
Normal file
@@ -0,0 +1,100 @@
|
||||
define(['underscore'], function(_) {
|
||||
'use strict';
|
||||
|
||||
var expectProfileElementContainsField = function(element, view) {
|
||||
var $element = $(element);
|
||||
var fieldTitle = $element.find('.u-field-title').text().trim();
|
||||
|
||||
if (!_.isUndefined(view.options.title)) {
|
||||
expect(fieldTitle).toBe(view.options.title);
|
||||
}
|
||||
|
||||
if ('fieldValue' in view) {
|
||||
expect(view.model.get(view.options.valueAttribute)).toBeTruthy();
|
||||
|
||||
if (view.fieldValue()) {
|
||||
expect(view.fieldValue()).toBe(view.modelValue());
|
||||
|
||||
} else if ('optionForValue' in view) {
|
||||
expect($($element.find('.u-field-value')[0]).text()).toBe(view.displayValue(view.modelValue()));
|
||||
|
||||
}else {
|
||||
expect($($element.find('.u-field-value')[0]).text()).toBe(view.modelValue());
|
||||
}
|
||||
} else {
|
||||
throw new Error('Unexpected field type: ' + view.fieldType);
|
||||
}
|
||||
};
|
||||
|
||||
var expectProfilePrivacyFieldTobeRendered = function(learnerProfileView, othersProfile) {
|
||||
|
||||
var accountPrivacyElement = learnerProfileView.$('.wrapper-profile-field-account-privacy');
|
||||
var privacyFieldElement = $(accountPrivacyElement).find('.u-field');
|
||||
|
||||
if (othersProfile) {
|
||||
expect(privacyFieldElement.length).toBe(0);
|
||||
} else {
|
||||
expect(privacyFieldElement.length).toBe(1);
|
||||
expectProfileElementContainsField(privacyFieldElement, learnerProfileView.options.accountPrivacyFieldView)
|
||||
}
|
||||
};
|
||||
|
||||
var expectSectionOneTobeRendered = function(learnerProfileView) {
|
||||
|
||||
var sectionOneFieldElements = $(learnerProfileView.$('.wrapper-profile-section-one')).find('.u-field');
|
||||
|
||||
expect(sectionOneFieldElements.length).toBe(learnerProfileView.options.sectionOneFieldViews.length);
|
||||
|
||||
_.each(sectionOneFieldElements, function (sectionFieldElement, fieldIndex) {
|
||||
expectProfileElementContainsField(sectionFieldElement, learnerProfileView.options.sectionOneFieldViews[fieldIndex]);
|
||||
});
|
||||
};
|
||||
|
||||
var expectSectionTwoTobeRendered = function(learnerProfileView) {
|
||||
|
||||
var sectionTwoElement = learnerProfileView.$('.wrapper-profile-section-two');
|
||||
var sectionTwoFieldElements = $(sectionTwoElement).find('.u-field');
|
||||
|
||||
expect(sectionTwoFieldElements.length).toBe(learnerProfileView.options.sectionTwoFieldViews.length);
|
||||
|
||||
_.each(sectionTwoFieldElements, function (sectionFieldElement, fieldIndex) {
|
||||
expectProfileElementContainsField(sectionFieldElement, learnerProfileView.options.sectionTwoFieldViews[fieldIndex]);
|
||||
});
|
||||
};
|
||||
|
||||
var expectProfileSectionsAndFieldsToBeRendered = function (learnerProfileView, othersProfile) {
|
||||
expectProfilePrivacyFieldTobeRendered(learnerProfileView, othersProfile);
|
||||
expectSectionOneTobeRendered(learnerProfileView);
|
||||
expectSectionTwoTobeRendered(learnerProfileView);
|
||||
};
|
||||
|
||||
var expectLimitedProfileSectionsAndFieldsToBeRendered = function (learnerProfileView, othersProfile) {
|
||||
expectProfilePrivacyFieldTobeRendered(learnerProfileView, othersProfile);
|
||||
|
||||
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]);
|
||||
});
|
||||
|
||||
if (othersProfile) {
|
||||
expect($('.profile-private--message').text()).toBe('This edX learner is currently sharing a limited profile.')
|
||||
} else {
|
||||
expect($('.profile-private--message').text()).toBe('You are currently sharing a limited profile.')
|
||||
}
|
||||
};
|
||||
|
||||
var expectProfileSectionsNotToBeRendered = function(learnerProfileView) {
|
||||
expect(learnerProfileView.$('.wrapper-profile-field-account-privacy').length).toBe(0);
|
||||
expect(learnerProfileView.$('.wrapper-profile-section-one').length).toBe(0);
|
||||
expect(learnerProfileView.$('.wrapper-profile-section-two').length).toBe(0);
|
||||
};
|
||||
|
||||
return {
|
||||
expectLimitedProfileSectionsAndFieldsToBeRendered: expectLimitedProfileSectionsAndFieldsToBeRendered,
|
||||
expectProfileSectionsAndFieldsToBeRendered: expectProfileSectionsAndFieldsToBeRendered,
|
||||
expectProfileSectionsNotToBeRendered: expectProfileSectionsNotToBeRendered
|
||||
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,150 @@
|
||||
define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'js/common_helpers/template_helpers',
|
||||
'js/spec/student_account/helpers',
|
||||
'js/spec/student_profile/helpers',
|
||||
'js/views/fields',
|
||||
'js/student_account/models/user_account_model',
|
||||
'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'
|
||||
],
|
||||
function (Backbone, $, _, AjaxHelpers, TemplateHelpers, Helpers, LearnerProfileHelpers, FieldViews, UserAccountModel, UserPreferencesModel,
|
||||
LearnerProfileView, LearnerProfileFields, LearnerProfilePage) {
|
||||
'use strict';
|
||||
|
||||
describe("edx.user.LearnerProfileFactory", function () {
|
||||
|
||||
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>');
|
||||
TemplateHelpers.installTemplate('templates/fields/field_readonly');
|
||||
TemplateHelpers.installTemplate('templates/fields/field_dropdown');
|
||||
TemplateHelpers.installTemplate('templates/fields/field_textarea');
|
||||
TemplateHelpers.installTemplate('templates/student_profile/learner_profile');
|
||||
});
|
||||
|
||||
it("show loading error when UserAccountModel fails to load", function() {
|
||||
|
||||
requests = AjaxHelpers.requests(this);
|
||||
|
||||
var context = LearnerProfilePage({
|
||||
'accounts_api_url': Helpers.USER_ACCOUNTS_API_URL,
|
||||
'preferences_api_url': Helpers.USER_PREFERENCES_API_URL,
|
||||
'own_profile': true,
|
||||
'account_settings_page_url': Helpers.USER_ACCOUNTS_API_URL,
|
||||
'country_options': Helpers.FIELD_OPTIONS,
|
||||
'language_options': Helpers.FIELD_OPTIONS,
|
||||
'has_preferences_access': true
|
||||
}),
|
||||
learnerProfileView = context.learnerProfileView;
|
||||
|
||||
Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true);
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
|
||||
LearnerProfileHelpers.expectProfileSectionsNotToBeRendered(learnerProfileView);
|
||||
|
||||
|
||||
var userAccountRequest = requests[0];
|
||||
expect(userAccountRequest.method).toBe('GET');
|
||||
expect(userAccountRequest.url).toBe(Helpers.USER_ACCOUNTS_API_URL);
|
||||
|
||||
AjaxHelpers.respondWithError(requests, 500);
|
||||
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, true);
|
||||
Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, false);
|
||||
LearnerProfileHelpers.expectProfileSectionsNotToBeRendered(learnerProfileView);
|
||||
});
|
||||
|
||||
it("shows loading error when UserPreferencesModel fails to load", function() {
|
||||
|
||||
requests = AjaxHelpers.requests(this);
|
||||
|
||||
var context = LearnerProfilePage({
|
||||
'accounts_api_url': Helpers.USER_ACCOUNTS_API_URL,
|
||||
'preferences_api_url': Helpers.USER_PREFERENCES_API_URL,
|
||||
'own_profile': true,
|
||||
'account_settings_page_url': Helpers.USER_ACCOUNTS_API_URL,
|
||||
'country_options': Helpers.FIELD_OPTIONS,
|
||||
'language_options': Helpers.FIELD_OPTIONS,
|
||||
'has_preferences_access': true
|
||||
}),
|
||||
learnerProfileView = context.learnerProfileView;
|
||||
|
||||
Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true);
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
|
||||
LearnerProfileHelpers.expectProfileSectionsNotToBeRendered(learnerProfileView);
|
||||
|
||||
var userAccountRequest = requests[0];
|
||||
expect(userAccountRequest.method).toBe('GET');
|
||||
expect(userAccountRequest.url).toBe(Helpers.USER_ACCOUNTS_API_URL);
|
||||
|
||||
AjaxHelpers.respondWithJson(requests, Helpers.USER_ACCOUNTS_DATA);
|
||||
Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true);
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
|
||||
LearnerProfileHelpers.expectProfileSectionsNotToBeRendered(learnerProfileView);
|
||||
|
||||
var userPreferencesRequest = requests[1];
|
||||
expect(userPreferencesRequest.method).toBe('GET');
|
||||
expect(userPreferencesRequest.url).toBe(Helpers.USER_PREFERENCES_API_URL);
|
||||
|
||||
AjaxHelpers.respondWithError(requests, 500);
|
||||
Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, false);
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, true);
|
||||
LearnerProfileHelpers.expectProfileSectionsNotToBeRendered(learnerProfileView);
|
||||
});
|
||||
|
||||
it("renders the limited profile after models are successfully fetched", function() {
|
||||
|
||||
requests = AjaxHelpers.requests(this);
|
||||
|
||||
var context = LearnerProfilePage({
|
||||
'accounts_api_url': Helpers.USER_ACCOUNTS_API_URL,
|
||||
'preferences_api_url': Helpers.USER_PREFERENCES_API_URL,
|
||||
'own_profile': true,
|
||||
'account_settings_page_url': Helpers.USER_ACCOUNTS_API_URL,
|
||||
'country_options': Helpers.FIELD_OPTIONS,
|
||||
'language_options': Helpers.FIELD_OPTIONS,
|
||||
'has_preferences_access': true
|
||||
});
|
||||
|
||||
var learnerProfileView = context.learnerProfileView;
|
||||
|
||||
Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true);
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
|
||||
LearnerProfileHelpers.expectProfileSectionsNotToBeRendered(learnerProfileView);
|
||||
|
||||
AjaxHelpers.respondWithJson(requests, Helpers.USER_ACCOUNTS_DATA);
|
||||
AjaxHelpers.respondWithJson(requests, Helpers.USER_PREFERENCES_DATA);
|
||||
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
|
||||
LearnerProfileHelpers.expectLimitedProfileSectionsAndFieldsToBeRendered(learnerProfileView)
|
||||
});
|
||||
|
||||
it("renders the full profile after models are successfully fetched", function() {
|
||||
|
||||
requests = AjaxHelpers.requests(this);
|
||||
|
||||
var context = LearnerProfilePage({
|
||||
'accounts_api_url': Helpers.USER_ACCOUNTS_API_URL,
|
||||
'preferences_api_url': Helpers.USER_PREFERENCES_API_URL,
|
||||
'own_profile': true,
|
||||
'account_settings_page_url': Helpers.USER_ACCOUNTS_API_URL,
|
||||
'country_options': Helpers.FIELD_OPTIONS,
|
||||
'language_options': Helpers.FIELD_OPTIONS,
|
||||
'has_preferences_access': true
|
||||
}),
|
||||
learnerProfileView = context.learnerProfileView;
|
||||
|
||||
Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true);
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
|
||||
LearnerProfileHelpers.expectProfileSectionsNotToBeRendered(learnerProfileView);
|
||||
|
||||
AjaxHelpers.respondWithJson(requests, Helpers.USER_ACCOUNTS_DATA);
|
||||
AjaxHelpers.respondWithJson(requests, Helpers.USER_PREFERENCES_DATA);
|
||||
|
||||
// sets the profile for full view.
|
||||
context.accountPreferencesModel.set({account_privacy: 'all_users'});
|
||||
LearnerProfileHelpers.expectProfileSectionsAndFieldsToBeRendered(learnerProfileView, false)
|
||||
});
|
||||
});
|
||||
});
|
||||
178
lms/static/js/spec/student_profile/learner_profile_view_spec.js
Normal file
178
lms/static/js/spec/student_profile/learner_profile_view_spec.js
Normal file
@@ -0,0 +1,178 @@
|
||||
define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'js/common_helpers/template_helpers',
|
||||
'js/spec/student_account/helpers',
|
||||
'js/spec/student_profile/helpers',
|
||||
'js/views/fields',
|
||||
'js/student_account/models/user_account_model',
|
||||
'js/student_account/models/user_preferences_model',
|
||||
'js/student_profile/views/learner_profile_fields',
|
||||
'js/student_profile/views/learner_profile_view',
|
||||
'js/student_account/views/account_settings_fields'
|
||||
],
|
||||
function (Backbone, $, _, AjaxHelpers, TemplateHelpers, Helpers, LearnerProfileHelpers, FieldViews, UserAccountModel,
|
||||
AccountPreferencesModel, LearnerProfileFields, LearnerProfileView, AccountSettingsFieldViews) {
|
||||
'use strict';
|
||||
|
||||
describe("edx.user.LearnerProfileView", function (options) {
|
||||
|
||||
var createLearnerProfileView = function (ownProfile, accountPrivacy, profileIsPublic) {
|
||||
|
||||
var accountSettingsModel = new UserAccountModel();
|
||||
accountSettingsModel.set(Helpers.USER_ACCOUNTS_DATA);
|
||||
accountSettingsModel.set({'profile_is_public': profileIsPublic});
|
||||
|
||||
var accountPreferencesModel = new AccountPreferencesModel();
|
||||
accountPreferencesModel.set({account_privacy: accountPrivacy});
|
||||
|
||||
accountPreferencesModel.url = Helpers.USER_PREFERENCES_API_URL;
|
||||
|
||||
var editable = ownProfile ? 'toggle' : 'never';
|
||||
|
||||
var accountPrivacyFieldView = new LearnerProfileFields.AccountPrivacyFieldView({
|
||||
model: accountPreferencesModel,
|
||||
required: true,
|
||||
editable: 'always',
|
||||
showMessages: false,
|
||||
title: 'edX learners can see my:',
|
||||
valueAttribute: "account_privacy",
|
||||
options: [
|
||||
['all_users', 'Full Profile'],
|
||||
['private', 'Limited Profile']
|
||||
],
|
||||
helpMessage: '',
|
||||
accountSettingsPageUrl: '/account/settings/'
|
||||
});
|
||||
|
||||
var usernameFieldView = new FieldViews.ReadonlyFieldView({
|
||||
model: accountSettingsModel,
|
||||
valueAttribute: "username",
|
||||
helpMessage: ""
|
||||
});
|
||||
|
||||
var sectionOneFieldViews = [
|
||||
usernameFieldView,
|
||||
new FieldViews.DropdownFieldView({
|
||||
model: accountSettingsModel,
|
||||
required: false,
|
||||
editable: editable,
|
||||
showMessages: false,
|
||||
iconName: 'fa-map-marker',
|
||||
placeholderValue: 'Add country',
|
||||
valueAttribute: "country",
|
||||
options: Helpers.FIELD_OPTIONS,
|
||||
helpMessage: ''
|
||||
}),
|
||||
|
||||
new AccountSettingsFieldViews.LanguageProficienciesFieldView({
|
||||
model: accountSettingsModel,
|
||||
required: false,
|
||||
editable: editable,
|
||||
showMessages: false,
|
||||
iconName: 'fa-comment',
|
||||
placeholderValue: 'Add language',
|
||||
valueAttribute: "language_proficiencies",
|
||||
options: Helpers.FIELD_OPTIONS,
|
||||
helpMessage: ''
|
||||
})
|
||||
];
|
||||
|
||||
var sectionTwoFieldViews = [
|
||||
new FieldViews.TextareaFieldView({
|
||||
model: accountSettingsModel,
|
||||
editable: editable,
|
||||
showMessages: false,
|
||||
title: 'About me',
|
||||
placeholderValue: "Tell other edX learners a little about yourself: where you live, what your interests are, why you're taking courses on edX, or what you hope to learn.",
|
||||
valueAttribute: "bio",
|
||||
helpMessage: ''
|
||||
})
|
||||
];
|
||||
|
||||
return new LearnerProfileView(
|
||||
{
|
||||
el: $('.wrapper-profile'),
|
||||
own_profile: ownProfile,
|
||||
has_preferences_access: true,
|
||||
accountSettingsModel: accountSettingsModel,
|
||||
preferencesModel: accountPreferencesModel,
|
||||
accountPrivacyFieldView: accountPrivacyFieldView,
|
||||
usernameFieldView: usernameFieldView,
|
||||
sectionOneFieldViews: sectionOneFieldViews,
|
||||
sectionTwoFieldViews: sectionTwoFieldViews
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
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>');
|
||||
TemplateHelpers.installTemplate('templates/fields/field_readonly');
|
||||
TemplateHelpers.installTemplate('templates/fields/field_dropdown');
|
||||
TemplateHelpers.installTemplate('templates/fields/field_textarea');
|
||||
TemplateHelpers.installTemplate('templates/student_profile/learner_profile');
|
||||
});
|
||||
|
||||
it("shows loading error correctly", function() {
|
||||
|
||||
var learnerProfileView = createLearnerProfileView(false, 'all_users');
|
||||
|
||||
Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true);
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
|
||||
|
||||
learnerProfileView.render();
|
||||
learnerProfileView.showLoadingError();
|
||||
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, true);
|
||||
});
|
||||
|
||||
it("renders all fields as expected for self with full access", function() {
|
||||
|
||||
var learnerProfileView = createLearnerProfileView(true, 'all_users', true);
|
||||
|
||||
Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true);
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
|
||||
|
||||
learnerProfileView.render();
|
||||
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
|
||||
LearnerProfileHelpers.expectProfileSectionsAndFieldsToBeRendered(learnerProfileView);
|
||||
});
|
||||
|
||||
it("renders all fields as expected for self with limited access", function() {
|
||||
|
||||
var learnerProfileView = createLearnerProfileView(true, 'private', false);
|
||||
|
||||
Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true);
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
|
||||
|
||||
learnerProfileView.render();
|
||||
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
|
||||
LearnerProfileHelpers.expectLimitedProfileSectionsAndFieldsToBeRendered(learnerProfileView);
|
||||
});
|
||||
|
||||
it("renders the fields as expected for others with full access", function() {
|
||||
|
||||
var learnerProfileView = createLearnerProfileView(false, 'all_users', true);
|
||||
|
||||
Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true);
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
|
||||
|
||||
learnerProfileView.render();
|
||||
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
|
||||
LearnerProfileHelpers.expectProfileSectionsAndFieldsToBeRendered(learnerProfileView, true)
|
||||
});
|
||||
|
||||
it("renders the fields as expected for others with limited access", function() {
|
||||
|
||||
var learnerProfileView = createLearnerProfileView(false, 'private', false);
|
||||
|
||||
Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true);
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
|
||||
|
||||
learnerProfileView.render();
|
||||
|
||||
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
|
||||
LearnerProfileHelpers.expectLimitedProfileSectionsAndFieldsToBeRendered(learnerProfileView, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -27,7 +27,8 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
model: fieldData.model || new UserAccountModel({}),
|
||||
title: fieldData.title || 'Field Title',
|
||||
valueAttribute: fieldData.valueAttribute,
|
||||
helpMessage: fieldData.helpMessage || 'I am a field message'
|
||||
helpMessage: fieldData.helpMessage || 'I am a field message',
|
||||
placeholderValue: fieldData.placeholderValue || 'I am a placeholder message'
|
||||
};
|
||||
|
||||
switch (fieldType) {
|
||||
@@ -58,8 +59,12 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
}
|
||||
};
|
||||
|
||||
var expectTitleAndMessageToBe = function(view, expectedTitle, expectedMessage) {
|
||||
var expectTitleToBe = function(view, expectedTitle) {
|
||||
expect(view.$('.u-field-title').text().trim()).toBe(expectedTitle);
|
||||
};
|
||||
|
||||
var expectTitleAndMessageToBe = function(view, expectedTitle, expectedMessage) {
|
||||
expectTitleToBe(view, expectedTitle);
|
||||
expect(view.$('.u-field-message').text().trim()).toBe(expectedMessage);
|
||||
};
|
||||
|
||||
@@ -125,9 +130,19 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
var request_data = {};
|
||||
var url = view.model.url;
|
||||
|
||||
expectTitleAndMessageToBe(view, data.title, data.helpMessage);
|
||||
if (data.editable === 'toggle') {
|
||||
expect(view.el).toHaveClass('mode-placeholder');
|
||||
expectTitleToBe(view, data.title);
|
||||
expectMessageContains(view, view.indicators['canEdit']);
|
||||
view.$el.click();
|
||||
} else {
|
||||
expectTitleAndMessageToBe(view, data.title, data.helpMessage);
|
||||
}
|
||||
|
||||
view.$(data.valueElementSelector).val(data.validValue).change();
|
||||
expect(view.el).toHaveClass('mode-edit');
|
||||
expect(view.fieldValue()).not.toBe(data.validValue);
|
||||
|
||||
view.$(data.valueInputSelector).val(data.validValue).change();
|
||||
// When the value in the field is changed
|
||||
expect(view.fieldValue()).toBe(data.validValue);
|
||||
expectMessageContains(view, view.indicators['inProgress']);
|
||||
@@ -139,9 +154,14 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
|
||||
AjaxHelpers.respondWithNoContent(requests);
|
||||
// When server returns success.
|
||||
expectMessageContains(view, view.indicators['success']);
|
||||
if (data.editable === 'toggle') {
|
||||
expect(view.el).toHaveClass('mode-display');
|
||||
view.$el.click();
|
||||
} else {
|
||||
expectMessageContains(view, view.indicators['success']);
|
||||
}
|
||||
|
||||
view.$(data.valueElementSelector).val(data.invalidValue1).change();
|
||||
view.$(data.valueInputSelector).val(data.invalidValue1).change();
|
||||
request_data[data.valueAttribute] = data.invalidValue1;
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'PATCH', url, request_data
|
||||
@@ -150,8 +170,9 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
// When server returns a 500 error
|
||||
expectMessageContains(view, view.indicators['error']);
|
||||
expectMessageContains(view, view.messages['error']);
|
||||
expect(view.el).toHaveClass('mode-edit');
|
||||
|
||||
view.$(data.valueElementSelector).val(data.invalidValue2).change();
|
||||
view.$(data.valueInputSelector).val(data.invalidValue2).change();
|
||||
request_data[data.valueAttribute] = data.invalidValue2;
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'PATCH', url, request_data
|
||||
@@ -160,12 +181,29 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
// When server returns a validation error
|
||||
expectMessageContains(view, view.indicators['validationError']);
|
||||
expectMessageContains(view, data.validationError);
|
||||
expect(view.el).toHaveClass('mode-edit');
|
||||
|
||||
view.$(data.valueInputSelector).val('').change();
|
||||
// When the value in the field is changed
|
||||
expect(view.fieldValue()).toBe('');
|
||||
request_data[data.valueAttribute] = '';
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'PATCH', url, request_data
|
||||
);
|
||||
AjaxHelpers.respondWithNoContent(requests);
|
||||
// When server returns success.
|
||||
if (data.editable === 'toggle') {
|
||||
expect(view.el).toHaveClass('mode-placeholder');
|
||||
} else {
|
||||
expect(view.el).toHaveClass('mode-edit');
|
||||
}
|
||||
};
|
||||
|
||||
var verifyTextField = function (view, data, requests) {
|
||||
var selector = '.u-field-value > input';
|
||||
verifyEditableField(view, _.extend({
|
||||
valueElementSelector: selector,
|
||||
valueSelector: '.u-field-value',
|
||||
valueInputSelector: '.u-field-value > input'
|
||||
}, data
|
||||
), requests);
|
||||
}
|
||||
@@ -173,7 +211,8 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
var verifyDropDownField = function (view, data, requests) {
|
||||
var selector = '.u-field-value > select';
|
||||
verifyEditableField(view, _.extend({
|
||||
valueElementSelector: selector,
|
||||
valueSelector: '.u-field-value',
|
||||
valueInputSelector: '.u-field-value > select'
|
||||
}, data
|
||||
), requests);
|
||||
}
|
||||
@@ -183,6 +222,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
UserAccountModel: UserAccountModel,
|
||||
createFieldData: createFieldData,
|
||||
createErrorMessage: createErrorMessage,
|
||||
expectTitleToBe: expectTitleToBe,
|
||||
expectTitleAndMessageToBe: expectTitleAndMessageToBe,
|
||||
expectMessageContains: expectMessageContains,
|
||||
expectAjaxRequestWithData: expectAjaxRequestWithData,
|
||||
|
||||
@@ -7,7 +7,8 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
|
||||
var USERNAME = 'Legolas',
|
||||
FULLNAME = 'Legolas Thranduil',
|
||||
EMAIL = 'legolas@woodland.middlearth';
|
||||
EMAIL = 'legolas@woodland.middlearth',
|
||||
BIO = "My Name is Theon Greyjoy. I'm member of House Greyjoy";
|
||||
|
||||
describe("edx.FieldViews", function () {
|
||||
|
||||
@@ -19,6 +20,8 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
FieldViews.TextFieldView,
|
||||
FieldViews.DropdownFieldView,
|
||||
FieldViews.LinkFieldView,
|
||||
FieldViews.TextareaFieldView
|
||||
|
||||
];
|
||||
|
||||
beforeEach(function () {
|
||||
@@ -26,6 +29,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
TemplateHelpers.installTemplate('templates/fields/field_dropdown');
|
||||
TemplateHelpers.installTemplate('templates/fields/field_link');
|
||||
TemplateHelpers.installTemplate('templates/fields/field_text');
|
||||
TemplateHelpers.installTemplate('templates/fields/field_textarea');
|
||||
|
||||
timerCallback = jasmine.createSpy('timerCallback');
|
||||
jasmine.Clock.useMock();
|
||||
@@ -55,7 +59,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
title: 'Username',
|
||||
valueAttribute: 'username',
|
||||
helpMessage: 'The username that you use to sign in to edX.'
|
||||
})
|
||||
});
|
||||
|
||||
var view = new fieldViewClass(fieldData).render();
|
||||
FieldViewsSpecHelpers.verifySuccessMessageReset(view, fieldData, timerCallback);
|
||||
@@ -66,7 +70,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
|
||||
requests = AjaxHelpers.requests(this);
|
||||
|
||||
var fieldViewClass = FieldViews.FieldView;
|
||||
var fieldViewClass = FieldViews.EditableFieldView;
|
||||
var fieldData = FieldViewsSpecHelpers.createFieldData(fieldViewClass, {
|
||||
title: 'Preferred Language',
|
||||
valueAttribute: 'language',
|
||||
@@ -101,7 +105,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
expect(view.$('.u-field-value input').val().trim()).toBe('bookworm');
|
||||
});
|
||||
|
||||
it("correctly renders, updates and persists changes to TextFieldView", function() {
|
||||
it("correctly renders, updates and persists changes to TextFieldView when editable == always", function() {
|
||||
|
||||
requests = AjaxHelpers.requests(this);
|
||||
|
||||
@@ -123,7 +127,28 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
}, requests);
|
||||
});
|
||||
|
||||
it("correctly renders, updates and persists changes to DropdownFieldView", function() {
|
||||
it("correctly renders and updates DropdownFieldView when editable == never", function() {
|
||||
|
||||
requests = AjaxHelpers.requests(this);
|
||||
|
||||
var fieldData = FieldViewsSpecHelpers.createFieldData(FieldViews.DropdownFieldView, {
|
||||
title: 'Full Name',
|
||||
valueAttribute: 'name',
|
||||
helpMessage: 'edX full name',
|
||||
editable: 'never'
|
||||
|
||||
});
|
||||
var view = new FieldViews.DropdownFieldView(fieldData).render();
|
||||
FieldViewsSpecHelpers.expectTitleAndMessageToBe(view, fieldData.title, fieldData.helpMessage);
|
||||
expect(view.el).toHaveClass('mode-hidden');
|
||||
|
||||
view.model.set({'name': fieldData.options[1][0]});
|
||||
expect(view.el).toHaveClass('mode-display');
|
||||
view.$el.click();
|
||||
expect(view.el).toHaveClass('mode-display');
|
||||
});
|
||||
|
||||
it("correctly renders, updates and persists changes to DropdownFieldView when editable == always", function() {
|
||||
|
||||
requests = AjaxHelpers.requests(this);
|
||||
|
||||
@@ -145,6 +170,93 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
}, requests);
|
||||
});
|
||||
|
||||
it("correctly renders, updates and persists changes to DropdownFieldView when editable == toggle", function() {
|
||||
|
||||
requests = AjaxHelpers.requests(this);
|
||||
|
||||
var fieldData = FieldViewsSpecHelpers.createFieldData(FieldViews.DropdownFieldView, {
|
||||
title: 'Full Name',
|
||||
valueAttribute: 'name',
|
||||
helpMessage: 'edX full name',
|
||||
editable: 'toggle'
|
||||
});
|
||||
var view = new FieldViews.DropdownFieldView(fieldData).render();
|
||||
|
||||
FieldViewsSpecHelpers.verifyDropDownField(view, {
|
||||
title: fieldData.title,
|
||||
valueAttribute: fieldData.valueAttribute,
|
||||
helpMessage: fieldData.helpMessage,
|
||||
editable: 'toggle',
|
||||
validValue: FieldViewsSpecHelpers.SELECT_OPTIONS[0][0],
|
||||
invalidValue1: FieldViewsSpecHelpers.SELECT_OPTIONS[1][0],
|
||||
invalidValue2: FieldViewsSpecHelpers.SELECT_OPTIONS[2][0],
|
||||
validationError: "Nope, this will not do!"
|
||||
}, requests);
|
||||
});
|
||||
|
||||
it("correctly renders and updates TextAreaFieldView when editable == never", function() {
|
||||
var fieldData = FieldViewsSpecHelpers.createFieldData(FieldViews.TextareaFieldView, {
|
||||
title: 'About me',
|
||||
valueAttribute: 'bio',
|
||||
helpMessage: 'Wicked is good',
|
||||
placeholderValue: "Tell other edX learners a little about yourself: where you live, what your interests are, why you’re taking courses on edX, or what you hope to learn.",
|
||||
editable: 'never'
|
||||
});
|
||||
|
||||
// set bio to empty to see the placeholder.
|
||||
fieldData.model.set({bio: ''});
|
||||
var view = new FieldViews.TextareaFieldView(fieldData).render();
|
||||
FieldViewsSpecHelpers.expectTitleAndMessageToBe(view, fieldData.title, fieldData.helpMessage);
|
||||
expect(view.el).toHaveClass('mode-hidden');
|
||||
expect(view.$('.u-field-value').text()).toBe(fieldData.placeholderValue);
|
||||
|
||||
var bio = 'Too much to tell!'
|
||||
view.model.set({'bio': bio});
|
||||
expect(view.el).toHaveClass('mode-display');
|
||||
expect(view.$('.u-field-value').text()).toBe(bio);
|
||||
view.$el.click();
|
||||
expect(view.el).toHaveClass('mode-display');
|
||||
});
|
||||
|
||||
it("correctly renders, updates and persists changes to TextAreaFieldView when editable == toggle", function() {
|
||||
|
||||
requests = AjaxHelpers.requests(this);
|
||||
|
||||
var valueInputSelector = '.u-field-value > textarea'
|
||||
var fieldData = FieldViewsSpecHelpers.createFieldData(FieldViews.TextareaFieldView, {
|
||||
title: 'About me',
|
||||
valueAttribute: 'bio',
|
||||
helpMessage: 'Wicked is good',
|
||||
placeholderValue: "Tell other edX learners a little about yourself: where you live, what your interests are, why you’re taking courses on edX, or what you hope to learn.",
|
||||
editable: 'toggle'
|
||||
|
||||
});
|
||||
fieldData.model.set({'bio': ''});
|
||||
|
||||
var view = new FieldViews.TextareaFieldView(fieldData).render();
|
||||
|
||||
FieldViewsSpecHelpers.expectTitleToBe(view, fieldData.title);
|
||||
FieldViewsSpecHelpers.expectMessageContains(view, view.indicators['canEdit']);
|
||||
expect(view.el).toHaveClass('mode-placeholder');
|
||||
expect(view.$('.u-field-value').text()).toBe(fieldData.placeholderValue);
|
||||
|
||||
view.$('.wrapper-u-field').click();
|
||||
expect(view.el).toHaveClass('mode-edit');
|
||||
view.$(valueInputSelector).val(BIO).focusout();
|
||||
expect(view.fieldValue()).toBe(BIO);
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'PATCH', view.model.url, {'bio': BIO}
|
||||
);
|
||||
AjaxHelpers.respondWithNoContent(requests);
|
||||
expect(view.el).toHaveClass('mode-display');
|
||||
|
||||
view.$('.wrapper-u-field').click();
|
||||
view.$(valueInputSelector).val('').focusout();
|
||||
AjaxHelpers.respondWithNoContent(requests);
|
||||
expect(view.el).toHaveClass('mode-placeholder');
|
||||
expect(view.$('.u-field-value').text()).toBe(fieldData.placeholderValue);
|
||||
});
|
||||
|
||||
it("correctly renders LinkFieldView", function() {
|
||||
var fieldData = FieldViewsSpecHelpers.createFieldData(FieldViews.LinkFieldView, {
|
||||
title: 'Title',
|
||||
@@ -157,5 +269,4 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
expect(view.$('.u-field-value > a').text().trim()).toBe(fieldData.linkTitle);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -19,7 +19,24 @@
|
||||
level_of_education: null,
|
||||
mailing_address: "",
|
||||
year_of_birth: null,
|
||||
language_proficiencies: []
|
||||
bio: null,
|
||||
language_proficiencies: [],
|
||||
requires_parental_consent: true,
|
||||
default_public_account_fields: []
|
||||
},
|
||||
|
||||
parse : function(response, xhr) {
|
||||
if (_.isNull(response)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Currently when a non-staff user A access user B's profile, the only way to tell whether user B's
|
||||
// profile is public is to check if the api has returned fields other than the default public fields
|
||||
// specified in settings.ACCOUNT_VISIBILITY_CONFIGURATION.
|
||||
var profileIsPublic = _.size(_.difference(_.keys(response), this.get('default_public_account_fields'))) > 0;
|
||||
this.set({'profile_is_public': profileIsPublic}, { silent: true });
|
||||
|
||||
return response;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
131
lms/static/js/student_profile/views/learner_profile_factory.js
Normal file
131
lms/static/js/student_profile/views/learner_profile_factory.js
Normal file
@@ -0,0 +1,131 @@
|
||||
;(function (define, undefined) {
|
||||
'use strict';
|
||||
define([
|
||||
'gettext', 'jquery', 'underscore', 'backbone',
|
||||
'js/student_account/models/user_account_model',
|
||||
'js/student_account/models/user_preferences_model',
|
||||
'js/views/fields',
|
||||
'js/student_profile/views/learner_profile_fields',
|
||||
'js/student_profile/views/learner_profile_view',
|
||||
'js/student_account/views/account_settings_fields'
|
||||
|
||||
|
||||
], function (gettext, $, _, Backbone, AccountSettingsModel, AccountPreferencesModel, FieldsView,
|
||||
LearnerProfileFieldsView, LearnerProfileView, AccountSettingsFieldViews) {
|
||||
|
||||
return function (options) {
|
||||
|
||||
var learnerProfileElement = $('.wrapper-profile');
|
||||
|
||||
var accountPreferencesModel = new AccountPreferencesModel();
|
||||
accountPreferencesModel.url = options['preferences_api_url'];
|
||||
|
||||
var accountSettingsModel = new AccountSettingsModel({
|
||||
'default_public_account_fields': options['default_public_account_fields']
|
||||
});
|
||||
accountSettingsModel.url = options['accounts_api_url'];
|
||||
|
||||
var editable = options['own_profile'] ? 'toggle' : 'never';
|
||||
|
||||
var accountPrivacyFieldView = new LearnerProfileFieldsView.AccountPrivacyFieldView({
|
||||
model: accountPreferencesModel,
|
||||
required: true,
|
||||
editable: 'always',
|
||||
showMessages: false,
|
||||
title: gettext('edX learners can see my:'),
|
||||
valueAttribute: "account_privacy",
|
||||
options: [
|
||||
['private', gettext('Limited Profile')],
|
||||
['all_users', gettext('Full Profile')]
|
||||
],
|
||||
helpMessage: '',
|
||||
accountSettingsPageUrl: options['account_settings_page_url']
|
||||
});
|
||||
|
||||
var usernameFieldView = new FieldsView.ReadonlyFieldView({
|
||||
model: accountSettingsModel,
|
||||
valueAttribute: "username",
|
||||
helpMessage: ""
|
||||
});
|
||||
|
||||
var sectionOneFieldViews = [
|
||||
usernameFieldView,
|
||||
new FieldsView.DropdownFieldView({
|
||||
model: accountSettingsModel,
|
||||
required: true,
|
||||
editable: editable,
|
||||
showMessages: false,
|
||||
iconName: 'fa-map-marker',
|
||||
placeholderValue: gettext('Add country'),
|
||||
valueAttribute: "country",
|
||||
options: options['country_options'],
|
||||
helpMessage: ''
|
||||
}),
|
||||
new AccountSettingsFieldViews.LanguageProficienciesFieldView({
|
||||
model: accountSettingsModel,
|
||||
required: false,
|
||||
editable: editable,
|
||||
showMessages: false,
|
||||
iconName: 'fa-comment',
|
||||
placeholderValue: gettext('Add language'),
|
||||
valueAttribute: "language_proficiencies",
|
||||
options: options['language_options'],
|
||||
helpMessage: ''
|
||||
})
|
||||
];
|
||||
|
||||
var sectionTwoFieldViews = [
|
||||
new FieldsView.TextareaFieldView({
|
||||
model: accountSettingsModel,
|
||||
editable: editable,
|
||||
showMessages: false,
|
||||
title: gettext('About me'),
|
||||
placeholderValue: gettext("Tell other edX learners a little about yourself: where you live, what your interests are, why you're taking courses on edX, or what you hope to learn."),
|
||||
valueAttribute: "bio",
|
||||
helpMessage: ''
|
||||
})
|
||||
];
|
||||
|
||||
var learnerProfileView = new LearnerProfileView({
|
||||
el: learnerProfileElement,
|
||||
own_profile: options['own_profile'],
|
||||
has_preferences_access: options['has_preferences_access'],
|
||||
accountSettingsModel: accountSettingsModel,
|
||||
preferencesModel: accountPreferencesModel,
|
||||
accountPrivacyFieldView: accountPrivacyFieldView,
|
||||
usernameFieldView: usernameFieldView,
|
||||
sectionOneFieldViews: sectionOneFieldViews,
|
||||
sectionTwoFieldViews: sectionTwoFieldViews
|
||||
});
|
||||
|
||||
var showLoadingError = function () {
|
||||
learnerProfileView.showLoadingError();
|
||||
};
|
||||
|
||||
var renderLearnerProfileView = function() {
|
||||
learnerProfileView.render();
|
||||
};
|
||||
|
||||
accountSettingsModel.fetch({
|
||||
success: function () {
|
||||
if (options['has_preferences_access']) {
|
||||
accountPreferencesModel.fetch({
|
||||
success: renderLearnerProfileView,
|
||||
error: showLoadingError
|
||||
});
|
||||
}
|
||||
else {
|
||||
renderLearnerProfileView();
|
||||
}
|
||||
},
|
||||
error: showLoadingError
|
||||
});
|
||||
|
||||
return {
|
||||
accountSettingsModel: accountSettingsModel,
|
||||
accountPreferencesModel: accountPreferencesModel,
|
||||
learnerProfileView: learnerProfileView
|
||||
};
|
||||
};
|
||||
})
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -0,0 +1,38 @@
|
||||
;(function (define, undefined) {
|
||||
'use strict';
|
||||
define([
|
||||
'gettext', 'jquery', 'underscore', 'backbone', 'js/views/fields', 'backbone-super'
|
||||
], function (gettext, $, _, Backbone, FieldViews) {
|
||||
|
||||
var LearnerProfileFieldViews = {};
|
||||
|
||||
LearnerProfileFieldViews.AccountPrivacyFieldView = FieldViews.DropdownFieldView.extend({
|
||||
|
||||
render: function () {
|
||||
this._super();
|
||||
this.message();
|
||||
return this;
|
||||
},
|
||||
|
||||
message: function () {
|
||||
if (this.profileIsPrivate) {
|
||||
this._super(interpolate_text(
|
||||
gettext("You must specify your birth year before you can share your full profile. To specify your birth year, go to the {account_settings_page_link}"),
|
||||
{'account_settings_page_link': '<a href="' + this.options.accountSettingsPageUrl + '">' + gettext('Account Settings page.') + '</a>'}
|
||||
));
|
||||
} else if (this.requiresParentalConsent) {
|
||||
this._super(interpolate_text(
|
||||
gettext('You must be over 13 to share a full profile. If you are over 13, make sure that you have specified a birth year on the {account_settings_page_link}'),
|
||||
{'account_settings_page_link': '<a href="' + this.options.accountSettingsPageUrl + '">' + gettext('Account Settings page.') + '</a>'}
|
||||
));
|
||||
}
|
||||
else {
|
||||
this._super('');
|
||||
}
|
||||
return this._super();
|
||||
}
|
||||
});
|
||||
|
||||
return LearnerProfileFieldViews;
|
||||
})
|
||||
}).call(this, define || RequireJS.define);
|
||||
71
lms/static/js/student_profile/views/learner_profile_view.js
Normal file
71
lms/static/js/student_profile/views/learner_profile_view.js
Normal file
@@ -0,0 +1,71 @@
|
||||
;(function (define, undefined) {
|
||||
'use strict';
|
||||
define([
|
||||
'gettext', 'jquery', 'underscore', 'backbone'
|
||||
], function (gettext, $, _, Backbone) {
|
||||
|
||||
var LearnerProfileView = Backbone.View.extend({
|
||||
|
||||
initialize: function (options) {
|
||||
this.template = _.template($('#learner_profile-tpl').text());
|
||||
_.bindAll(this, 'showFullProfile', 'render', 'renderFields', 'showLoadingError');
|
||||
this.listenTo(this.options.preferencesModel, "change:" + 'account_privacy', this.render);
|
||||
},
|
||||
|
||||
showFullProfile: function () {
|
||||
if (this.options.own_profile) {
|
||||
return this.options.preferencesModel.get('account_privacy') === 'all_users';
|
||||
} else {
|
||||
return this.options.accountSettingsModel.get('profile_is_public');
|
||||
}
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(this.template({
|
||||
username: this.options.accountSettingsModel.get('username'),
|
||||
profilePhoto: 'http://www.teachthought.com/wp-content/uploads/2012/07/edX-120x120.jpg',
|
||||
ownProfile: this.options.own_profile,
|
||||
showFullProfile: this.showFullProfile()
|
||||
}));
|
||||
this.renderFields();
|
||||
return this;
|
||||
},
|
||||
|
||||
renderFields: function() {
|
||||
var view = this;
|
||||
|
||||
if (this.options.own_profile) {
|
||||
var fieldView = this.options.accountPrivacyFieldView;
|
||||
fieldView.profileIsPrivate = (!this.options.accountSettingsModel.get('year_of_birth'));
|
||||
fieldView.requiresParentalConsent = (this.options.accountSettingsModel.get('requires_parental_consent'));
|
||||
fieldView.undelegateEvents();
|
||||
this.$('.wrapper-profile-field-account-privacy').append(fieldView.render().el);
|
||||
fieldView.delegateEvents();
|
||||
}
|
||||
|
||||
this.$('.profile-section-one-fields').append(this.options.usernameFieldView.render().el);
|
||||
|
||||
if (this.showFullProfile()) {
|
||||
_.each(this.options.sectionOneFieldViews, function (fieldView, index) {
|
||||
fieldView.undelegateEvents();
|
||||
view.$('.profile-section-one-fields').append(fieldView.render().el);
|
||||
fieldView.delegateEvents();
|
||||
});
|
||||
|
||||
_.each(this.options.sectionTwoFieldViews, function (fieldView, index) {
|
||||
fieldView.undelegateEvents();
|
||||
view.$('.profile-section-two-fields').append(fieldView.render().el);
|
||||
fieldView.delegateEvents();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
showLoadingError: function () {
|
||||
this.$('.ui-loading-indicator').addClass('is-hidden');
|
||||
this.$('.ui-loading-error').removeClass('is-hidden');
|
||||
}
|
||||
});
|
||||
|
||||
return LearnerProfileView;
|
||||
})
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -10,7 +10,7 @@
|
||||
var FieldViews = {};
|
||||
|
||||
FieldViews.FieldView = Backbone.View.extend({
|
||||
|
||||
|
||||
fieldType: 'generic',
|
||||
|
||||
className: function () {
|
||||
@@ -20,13 +20,16 @@
|
||||
tagName: 'div',
|
||||
|
||||
indicators: {
|
||||
'error': '<i class="fa fa-exclamation-triangle message-error"></i>',
|
||||
'validationError': '<i class="fa fa-exclamation-triangle message-validation-error"></i>',
|
||||
'inProgress': '<i class="fa fa-spinner message-in-progress"></i>',
|
||||
'success': '<i class="fa fa-check message-success"></i>'
|
||||
'canEdit': '<i class="icon fa fa-pencil message-can-edit" aria-hidden="true"></i>',
|
||||
'error': '<i class="fa fa-exclamation-triangle message-error" aria-hidden="true"></i>',
|
||||
'validationError': '<i class="fa fa-exclamation-triangle message-validation-error" aria-hidden="true"></i>',
|
||||
'inProgress': '<i class="fa fa-spinner fa-pulse message-in-progress" aria-hidden="true"></i>',
|
||||
'success': '<i class="fa fa-check message-success" aria-hidden="true"></i>',
|
||||
'plus': '<i class="fa fa-plus placeholder" aria-hidden="true"></i>'
|
||||
},
|
||||
|
||||
messages: {
|
||||
'canEdit': '',
|
||||
'error': gettext('An error occurred. Please try again.'),
|
||||
'validationError': '',
|
||||
'inProgress': gettext('Saving'),
|
||||
@@ -40,39 +43,26 @@
|
||||
this.helpMessage = this.options.helpMessage || '';
|
||||
this.showMessages = _.isUndefined(this.options.showMessages) ? true : this.options.showMessages;
|
||||
|
||||
_.bindAll(this, 'modelValue', 'saveAttributes', 'saveSucceeded', 'getMessage',
|
||||
'message', 'showHelpMessage', 'showInProgressMessage', 'showSuccessMessage', 'showErrorMessage');
|
||||
_.bindAll(this, 'modelValue', 'modelValueIsSet', 'message', 'getMessage', 'title',
|
||||
'showHelpMessage', 'showInProgressMessage', 'showSuccessMessage', 'showErrorMessage');
|
||||
},
|
||||
|
||||
modelValue: function () {
|
||||
return this.model.get(this.options.valueAttribute);
|
||||
},
|
||||
|
||||
saveAttributes: function (attributes, options) {
|
||||
var view = this;
|
||||
var defaultOptions = {
|
||||
contentType: 'application/merge-patch+json',
|
||||
patch: true,
|
||||
wait: true,
|
||||
success: function (model, response, options) {
|
||||
view.saveSucceeded()
|
||||
},
|
||||
error: function (model, xhr, options) {
|
||||
view.showErrorMessage(xhr)
|
||||
}
|
||||
};
|
||||
this.showInProgressMessage();
|
||||
this.model.save(attributes, _.extend(defaultOptions, options));
|
||||
},
|
||||
|
||||
saveSucceeded: function () {
|
||||
this.showSuccessMessage();
|
||||
modelValueIsSet: function() {
|
||||
return (this.modelValue() == true);
|
||||
},
|
||||
|
||||
message: function (message) {
|
||||
return this.$('.u-field-message').html(message);
|
||||
},
|
||||
|
||||
title: function (text) {
|
||||
return this.$('.u-field-title').html(text);
|
||||
},
|
||||
|
||||
getMessage: function(message_status) {
|
||||
if ((message_status + 'Message') in this) {
|
||||
return this[message_status + 'Message'].call(this);
|
||||
@@ -82,6 +72,14 @@
|
||||
return this.indicators[message_status];
|
||||
},
|
||||
|
||||
showCanEditMessage: function(show) {
|
||||
if (!_.isUndefined(show) && show) {
|
||||
this.message(this.getMessage('canEdit'));
|
||||
} else {
|
||||
this.message('');
|
||||
}
|
||||
},
|
||||
|
||||
showHelpMessage: function () {
|
||||
this.message(this.helpMessage);
|
||||
},
|
||||
@@ -126,6 +124,84 @@
|
||||
}
|
||||
});
|
||||
|
||||
FieldViews.EditableFieldView = FieldViews.FieldView.extend({
|
||||
|
||||
initialize: function (options) {
|
||||
_.bindAll(this, 'saveAttributes', 'saveSucceeded', 'showDisplayMode', 'showEditMode', 'startEditing', 'finishEditing');
|
||||
this._super(options);
|
||||
|
||||
this.editable = _.isUndefined(this.options.editable) ? 'always': this.options.editable;
|
||||
this.$el.addClass('editable-' + this.editable);
|
||||
|
||||
if (this.editable === 'always') {
|
||||
this.showEditMode(false);
|
||||
} else {
|
||||
this.showDisplayMode(false);
|
||||
}
|
||||
},
|
||||
|
||||
saveAttributes: function (attributes, options) {
|
||||
var view = this;
|
||||
var defaultOptions = {
|
||||
contentType: 'application/merge-patch+json',
|
||||
patch: true,
|
||||
wait: true,
|
||||
success: function () {
|
||||
view.saveSucceeded();
|
||||
},
|
||||
error: function (model, xhr) {
|
||||
view.showErrorMessage(xhr);
|
||||
}
|
||||
};
|
||||
this.showInProgressMessage();
|
||||
this.model.save(attributes, _.extend(defaultOptions, options));
|
||||
},
|
||||
|
||||
saveSucceeded: function () {
|
||||
this.showSuccessMessage();
|
||||
},
|
||||
|
||||
showDisplayMode: function(render) {
|
||||
this.mode = 'display';
|
||||
if (render) { this.render(); }
|
||||
|
||||
this.$el.removeClass('mode-edit');
|
||||
|
||||
this.$el.toggleClass('mode-hidden', (this.editable === 'never' && !this.modelValueIsSet()));
|
||||
this.$el.toggleClass('mode-placeholder', (this.editable === 'toggle' && !this.modelValueIsSet()));
|
||||
this.$el.toggleClass('mode-display', (this.modelValueIsSet()));
|
||||
},
|
||||
|
||||
showEditMode: function(render) {
|
||||
this.mode = 'edit';
|
||||
if (render) { this.render(); }
|
||||
|
||||
this.$el.removeClass('mode-hidden');
|
||||
this.$el.removeClass('mode-placeholder');
|
||||
this.$el.removeClass('mode-display');
|
||||
|
||||
this.$el.addClass('mode-edit');
|
||||
},
|
||||
|
||||
startEditing: function (event) {
|
||||
if (this.editable === 'toggle' && this.mode !== 'edit') {
|
||||
this.showEditMode(true);
|
||||
}
|
||||
},
|
||||
|
||||
finishEditing: function(event) {
|
||||
if (this.fieldValue() !== this.modelValue()) {
|
||||
this.saveValue();
|
||||
} else {
|
||||
if (this.editable === 'always') {
|
||||
this.showEditMode(true);
|
||||
} else {
|
||||
this.showDisplayMode(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
FieldViews.ReadonlyFieldView = FieldViews.FieldView.extend({
|
||||
|
||||
fieldType: 'readonly',
|
||||
@@ -157,7 +233,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
FieldViews.TextFieldView = FieldViews.FieldView.extend({
|
||||
FieldViews.TextFieldView = FieldViews.EditableFieldView.extend({
|
||||
|
||||
fieldType: 'text',
|
||||
|
||||
@@ -199,47 +275,188 @@
|
||||
}
|
||||
});
|
||||
|
||||
FieldViews.DropdownFieldView = FieldViews.FieldView.extend({
|
||||
FieldViews.DropdownFieldView = FieldViews.EditableFieldView.extend({
|
||||
|
||||
fieldType: 'dropdown',
|
||||
|
||||
templateSelector: '#field_dropdown-tpl',
|
||||
|
||||
events: {
|
||||
'change select': 'saveValue'
|
||||
'click': 'startEditing',
|
||||
'change select': 'finishEditing',
|
||||
'focusout select': 'finishEditing'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
_.bindAll(this, 'render', 'optionForValue', 'fieldValue', 'displayValue', 'updateValueInField', 'saveValue');
|
||||
this._super(options);
|
||||
_.bindAll(this, 'render', 'fieldValue', 'updateValueInField', 'saveValue');
|
||||
|
||||
this.listenTo(this.model, "change:" + this.options.valueAttribute, this.updateValueInField);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(this.template({
|
||||
id: this.options.valueAttribute,
|
||||
mode: this.mode,
|
||||
title: this.options.title,
|
||||
iconName: this.options.iconName,
|
||||
required: this.options.required,
|
||||
selectOptions: this.options.options,
|
||||
message: this.helpMessage
|
||||
}));
|
||||
|
||||
this.updateValueInField();
|
||||
|
||||
if (this.editable === 'toggle') {
|
||||
this.showCanEditMessage(this.mode === 'display');
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
modelValueIsSet: function() {
|
||||
var value = this.modelValue();
|
||||
if (_.isUndefined(value) || _.isNull(value) || value == '') {
|
||||
return false;
|
||||
} else {
|
||||
return !(_.isUndefined(this.optionForValue(value)))
|
||||
}
|
||||
},
|
||||
|
||||
optionForValue: function(value) {
|
||||
return _.find(this.options.options, function(option) { return option[0] == value; })
|
||||
},
|
||||
|
||||
fieldValue: function () {
|
||||
return this.$('.u-field-value select').val();
|
||||
},
|
||||
|
||||
displayValue: function (value) {
|
||||
if (value) {
|
||||
var option = this.optionForValue(value);
|
||||
return (option ? option[1] : '');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
updateValueInField: function () {
|
||||
var value = (_.isUndefined(this.modelValue()) || _.isNull(this.modelValue())) ? '' : this.modelValue();
|
||||
this.$('.u-field-value select').val(Mustache.escapeHtml(value));
|
||||
if (this.mode === 'display') {
|
||||
var value = this.displayValue(this.modelValue() || '');
|
||||
if (this.modelValueIsSet() === false) {
|
||||
value = this.options.placeholderValue || '';
|
||||
}
|
||||
this.$('.u-field-value').html(Mustache.escapeHtml(value));
|
||||
this.showDisplayMode(false);
|
||||
} else {
|
||||
this.$('.u-field-value select').val(this.modelValue() || '');
|
||||
}
|
||||
},
|
||||
|
||||
saveValue: function () {
|
||||
var attributes = {};
|
||||
attributes[this.options.valueAttribute] = this.fieldValue();
|
||||
this.saveAttributes(attributes);
|
||||
},
|
||||
|
||||
showEditMode: function(render) {
|
||||
this._super(render);
|
||||
if (this.editable === 'toggle') {
|
||||
this.$('.u-field-value select').focus();
|
||||
}
|
||||
},
|
||||
|
||||
saveSucceeded: function() {
|
||||
this._super();
|
||||
if (this.editable === 'toggle') {
|
||||
this.showDisplayMode(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
FieldViews.TextareaFieldView = FieldViews.EditableFieldView.extend({
|
||||
|
||||
fieldType: 'textarea',
|
||||
|
||||
templateSelector: '#field_textarea-tpl',
|
||||
|
||||
events: {
|
||||
'click .wrapper-u-field': 'startEditing',
|
||||
'click .u-field-placeholder': 'startEditing',
|
||||
'focusout textarea': 'finishEditing',
|
||||
'change textarea': 'adjustTextareaHeight',
|
||||
'keyup textarea': 'adjustTextareaHeight',
|
||||
'keydown textarea': 'adjustTextareaHeight',
|
||||
'paste textarea': 'adjustTextareaHeight',
|
||||
'cut textarea': 'adjustTextareaHeight'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
_.bindAll(this, 'render', 'adjustTextareaHeight', 'fieldValue', 'saveValue', 'updateView');
|
||||
this._super(options);
|
||||
this.listenTo(this.model, "change:" + this.options.valueAttribute, this.updateView);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var value = this.modelValue();
|
||||
if (this.mode === 'display') {
|
||||
value = value || this.options.placeholderValue;
|
||||
}
|
||||
this.$el.html(this.template({
|
||||
id: this.options.valueAttribute,
|
||||
mode: this.mode,
|
||||
value: value,
|
||||
message: this.helpMessage
|
||||
}));
|
||||
|
||||
this.title((this.modelValue() || this.mode === 'edit') ? this.options.title : this.indicators['plus'] + this.options.title);
|
||||
|
||||
if (this.editable === 'toggle') {
|
||||
this.showCanEditMessage(this.mode === 'display');
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
adjustTextareaHeight: function(event) {
|
||||
var textarea = this.$('textarea');
|
||||
textarea.css('height', 'auto').css('height', textarea.prop('scrollHeight') + 10);
|
||||
},
|
||||
|
||||
modelValue: function() {
|
||||
var value = this._super();
|
||||
return value ? $.trim(value) : '';
|
||||
},
|
||||
|
||||
fieldValue: function () {
|
||||
return this.$('.u-field-value textarea').val();
|
||||
},
|
||||
|
||||
saveValue: function () {
|
||||
var attributes = {};
|
||||
attributes[this.options.valueAttribute] = this.fieldValue();
|
||||
this.saveAttributes(attributes);
|
||||
},
|
||||
|
||||
updateView: function () {
|
||||
if (this.mode !== 'edit') {
|
||||
this.showDisplayMode(true);
|
||||
}
|
||||
},
|
||||
|
||||
modelValueIsSet: function() {
|
||||
return !(this.modelValue() === '');
|
||||
},
|
||||
|
||||
showEditMode: function(render) {
|
||||
this._super(render);
|
||||
this.adjustTextareaHeight();
|
||||
this.$('.u-field-value textarea').focus();
|
||||
},
|
||||
|
||||
saveSucceeded: function() {
|
||||
this._super();
|
||||
if (this.editable === 'toggle') {
|
||||
this.showDisplayMode(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
|
||||
// base - specific views
|
||||
@import "views/account-settings";
|
||||
@import "views/learner-profile";
|
||||
@import 'views/login-register';
|
||||
@import 'views/verification';
|
||||
@import 'views/decoupled-verification';
|
||||
|
||||
@@ -5,6 +5,49 @@
|
||||
.u-field {
|
||||
padding: $baseline 0;
|
||||
border-bottom: 1px solid $gray-l5;
|
||||
border: 1px dashed transparent;
|
||||
|
||||
&.mode-placeholder {
|
||||
border: 2px dashed transparent;
|
||||
border-radius: 3px;
|
||||
|
||||
span {
|
||||
color: $gray-l1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border: 2px dashed $link-color;
|
||||
|
||||
span {
|
||||
color: $link-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.editable-toggle.mode-display:hover {
|
||||
background-color: $m-blue-l4;
|
||||
border-radius: 3px;
|
||||
|
||||
.message-can-edit {
|
||||
display: inline-block;
|
||||
color: $link-color;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.mode-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
i {
|
||||
color: $gray-l2;
|
||||
vertical-align:text-bottom;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.message-can-edit {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.message-error {
|
||||
color: $alert-color;
|
||||
@@ -33,10 +76,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
.u-field-icon {
|
||||
width: $baseline;
|
||||
color: $gray-l2;
|
||||
}
|
||||
|
||||
.u-field-title {
|
||||
width: flex-grid(3, 12);
|
||||
display: inline-block;
|
||||
color: $dark-gray1;
|
||||
color: $gray;
|
||||
vertical-align: top;
|
||||
margin-bottom: 0;
|
||||
|
||||
@@ -56,12 +104,12 @@
|
||||
}
|
||||
|
||||
.u-field-message {
|
||||
@extend small;
|
||||
@extend %t-copy-sub1;
|
||||
@include padding-left($baseline/2);
|
||||
width: flex-grid(6, 12);
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
color: $dark-gray1;
|
||||
color: $gray-l1;
|
||||
|
||||
i {
|
||||
@include margin-right($baseline/4);
|
||||
|
||||
199
lms/static/sass/views/_learner-profile.scss
Normal file
199
lms/static/sass/views/_learner-profile.scss
Normal file
@@ -0,0 +1,199 @@
|
||||
// lms - application - learner profile
|
||||
// ====================
|
||||
|
||||
// Table of Contents
|
||||
// * +Container - Learner Profile
|
||||
// * +Main - Header
|
||||
// * +Settings Section
|
||||
|
||||
.view-profile {
|
||||
$profile-photo-dimension: 120px;
|
||||
|
||||
.content-wrapper {
|
||||
background-color: $white;
|
||||
}
|
||||
|
||||
.ui-loading-indicator {
|
||||
@extend .ui-loading-base;
|
||||
padding-bottom: $baseline;
|
||||
|
||||
// center horizontally
|
||||
@include margin-left(auto);
|
||||
@include margin-right(auto);
|
||||
width: ($baseline*5);
|
||||
}
|
||||
|
||||
.wrapper-profile {
|
||||
min-height: 200px;
|
||||
|
||||
.ui-loading-indicator {
|
||||
margin-top: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-self {
|
||||
.wrapper-profile-field-account-privacy {
|
||||
@include clearfix();
|
||||
@include box-sizing(border-box);
|
||||
margin: 0 auto 0;
|
||||
padding: ($baseline*0.75) 0;
|
||||
width: 100%;
|
||||
background-color: $gray-l3;
|
||||
|
||||
.u-field-account_privacy {
|
||||
@extend .container;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding: 0 ($baseline*1.5);
|
||||
}
|
||||
|
||||
.u-field-title {
|
||||
width: auto;
|
||||
color: $base-font-color;
|
||||
font-weight: $font-bold;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.u-field-value {
|
||||
width: auto;
|
||||
@include margin-left($baseline/2);
|
||||
}
|
||||
|
||||
.u-field-message {
|
||||
@include float(left);
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
color: $base-font-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper-profile-sections {
|
||||
@extend .container;
|
||||
padding: 0 ($baseline*1.5);
|
||||
}
|
||||
|
||||
.wrapper-profile-section-one {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
margin-top: ($baseline*1.5);
|
||||
|
||||
.profile-photo {
|
||||
@include float(left);
|
||||
height: $profile-photo-dimension;
|
||||
width: $profile-photo-dimension;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-section-one-fields {
|
||||
float: left;
|
||||
width: flex-grid(4, 12);
|
||||
@include margin-left($baseline*1.5);
|
||||
|
||||
.u-field {
|
||||
margin-bottom: ($baseline/4);
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
@include padding-left(3px);
|
||||
}
|
||||
|
||||
.u-field-username {
|
||||
margin-bottom: ($baseline/2);
|
||||
|
||||
input[type="text"] {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.u-field-value {
|
||||
width: 350px;
|
||||
@extend %t-title4;
|
||||
}
|
||||
}
|
||||
|
||||
.u-field-title {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.u-field-value {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.u-field-message {
|
||||
@include float(right);
|
||||
width: 20px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper-profile-section-two {
|
||||
width: flex-grid(8, 12);
|
||||
margin-top: ($baseline*1.5);
|
||||
}
|
||||
|
||||
.profile-section-two-fields {
|
||||
|
||||
.u-field-textarea {
|
||||
margin-bottom: ($baseline/2);
|
||||
padding: ($baseline/4) ($baseline/2) ($baseline/2);
|
||||
}
|
||||
|
||||
.u-field-title {
|
||||
font-size: 1.1em;
|
||||
@extend %t-weight4;
|
||||
margin-bottom: ($baseline/4);
|
||||
}
|
||||
|
||||
.u-field-value {
|
||||
width: 100%;
|
||||
white-space: pre-line;
|
||||
line-height: 1.5em;
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.u-field-message {
|
||||
@include float(right);
|
||||
width: auto;
|
||||
padding-top: ($baseline/4);
|
||||
}
|
||||
|
||||
.u-field.mode-placeholder {
|
||||
padding: $baseline;
|
||||
border: 2px dashed $gray-l3;
|
||||
i {
|
||||
font-size: 12px;
|
||||
padding-right: 5px;
|
||||
vertical-align: middle;
|
||||
color: $gray;
|
||||
}
|
||||
.u-field-title {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.u-field-value {
|
||||
text-align: center;
|
||||
line-height: 1.5em;
|
||||
@extend %t-copy-sub1;
|
||||
color: $gray;
|
||||
}
|
||||
}
|
||||
|
||||
.u-field.mode-placeholder:hover {
|
||||
border: 2px dashed $link-color;
|
||||
.u-field-title,
|
||||
i {
|
||||
color: $link-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,26 @@
|
||||
<label class="u-field-title" for="u-field-select-<%- id %>">
|
||||
<%- gettext(title) %>
|
||||
</label>
|
||||
<% if (title) { %>
|
||||
<label class="u-field-title" for="u-field-select-<%- id %>">
|
||||
<%- gettext(title) %>
|
||||
</label>
|
||||
<% } %>
|
||||
|
||||
<% if (iconName) { %>
|
||||
<i class="u-field-icon icon fa <%- iconName %> fa-fw" area-hidden="true" ></i>
|
||||
<% } %>
|
||||
|
||||
<span class="u-field-value">
|
||||
<select name="select" id="u-field-select-<%- id %>" aria-describedby="u-field-message-<%- id %>">
|
||||
<% if (!required) { %>
|
||||
<option value=""></option>
|
||||
<% } %>
|
||||
<% _.each(selectOptions, function(selectOption) { %>
|
||||
<option value="<%- selectOption[0] %>"><%- selectOption[1] %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
<% if (mode === 'edit') { %>
|
||||
<select name="select" id="u-field-select-<%- id %>" aria-describedby="u-field-message-<%- id %>">
|
||||
<% if (!required) { %>
|
||||
<option value=""></option>
|
||||
<% } %>
|
||||
<% _.each(selectOptions, function(selectOption) { %>
|
||||
<option value="<%- selectOption[0] %>"><%- selectOption[1] %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
<% } %>
|
||||
</span>
|
||||
|
||||
<span class="u-field-message" id="u-field-message-<%- id %>">
|
||||
<%- gettext(message) %>
|
||||
</span>
|
||||
|
||||
14
lms/templates/fields/field_textarea.underscore
Normal file
14
lms/templates/fields/field_textarea.underscore
Normal file
@@ -0,0 +1,14 @@
|
||||
<div class="wrapper-u-field">
|
||||
<div class="u-field-header">
|
||||
<label class="u-field-title" for="u-field-textarea-<%- id %>" aria-describedby="u-field-message-<%- id %>"></label>
|
||||
<span class="u-field-message" id="u-field-message-<%- id %>"><%- message %></span>
|
||||
</div>
|
||||
|
||||
<div class="u-field-value"><%
|
||||
if (mode === 'edit') {
|
||||
%><textarea id="u-field-textarea-<%- id %>" rows="4"><%- value %></textarea><%
|
||||
} else {
|
||||
%><%- value %><%
|
||||
}
|
||||
%></div>
|
||||
</div>
|
||||
@@ -83,8 +83,9 @@ site_status_msg = get_site_status_msg(course_id)
|
||||
<ul class="dropdown-menu" aria-label="More Options" role="menu">
|
||||
<%block name="navigation_dropdown_menu_links" >
|
||||
<li><a href="${reverse('account_settings')}">${_("Account Settings")}</a></li>
|
||||
<li><a href="${reverse('learner_profile', kwargs={'username': user.username})}">${_("My Profile")}</a></li>
|
||||
</%block>
|
||||
<li><a href="${reverse('logout')}" role="menuitem">${_("Sign out")}</a></li>
|
||||
<li><a href="${reverse('logout')}" role="menuitem">${_("Sign Out")}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -91,8 +91,9 @@ site_status_msg = get_site_status_msg(course_id)
|
||||
<ul class="dropdown-menu" aria-label="More Options" role="menu">
|
||||
<%block name="navigation_dropdown_menu_links" >
|
||||
<li><a href="${reverse('account_settings')}">${_("Account Settings")}</a></li>
|
||||
<li><a href="${reverse('learner_profile', kwargs={'username': user.username})}">${_("My Profile")}</a></li>
|
||||
</%block>
|
||||
<li><a href="${reverse('logout')}" role="menuitem">${_("Sign out")}</a></li>
|
||||
<li><a href="${reverse('logout')}" role="menuitem">${_("Sign Out")}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
43
lms/templates/student_profile/learner_profile.html
Normal file
43
lms/templates/student_profile/learner_profile.html
Normal file
@@ -0,0 +1,43 @@
|
||||
<%! import json %>
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
|
||||
<%inherit file="/main.html" />
|
||||
<%namespace name='static' file='/static_content.html'/>
|
||||
|
||||
<%block name="pagetitle">${_("Learner Profile")}</%block>
|
||||
|
||||
<%block name="bodyclass">view-profile</%block>
|
||||
|
||||
<%block name="header_extras">
|
||||
% for template_name in ["field_dropdown", "field_textarea", "field_readonly"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="fields/${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
|
||||
% for template_name in ["learner_profile",]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="student_profile/${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
</%block>
|
||||
|
||||
<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>
|
||||
<%block name="headextra">
|
||||
<%static:css group='style-course'/>
|
||||
|
||||
<script>
|
||||
(function (require) {
|
||||
require(['js/student_profile/views/learner_profile_factory'], function(setupLearnerProfile) {
|
||||
var options = ${ json.dumps(data) };
|
||||
setupLearnerProfile(options);
|
||||
});
|
||||
}).call(this, require || RequireJS.require);
|
||||
</script>
|
||||
|
||||
</%block>
|
||||
29
lms/templates/student_profile/learner_profile.underscore
Normal file
29
lms/templates/student_profile/learner_profile.underscore
Normal file
@@ -0,0 +1,29 @@
|
||||
<div class="profile <%- ownProfile ? 'profile-self' : 'profile-other' %>">
|
||||
<div class="wrapper-profile-field-account-privacy"></div>
|
||||
|
||||
<div class="wrapper-profile-sections account-settings-container">
|
||||
<div class="wrapper-profile-section-one">
|
||||
<div class="profile-photo">
|
||||
<img src="<%- profilePhoto %>" alt="Profile image for <%- username %>">
|
||||
</div>
|
||||
|
||||
<div class="profile-section-one-fields">
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui-loading-error is-hidden">
|
||||
<i class="fa fa-exclamation-triangle message-error" aria-hidden=true></i>
|
||||
<span class="copy"><%- gettext("An error occurred. Please reload the page.") %></span>
|
||||
</div>
|
||||
<div class="wrapper-profile-section-two">
|
||||
<div class="profile-section-two-fields">
|
||||
<% if (!showFullProfile) { %>
|
||||
<% if(ownProfile) { %>
|
||||
<span class="profile-private--message"><%- gettext("You are currently sharing a limited profile.") %></span>
|
||||
<% } else { %>
|
||||
<span class="profile-private--message"><%- gettext("This edX learner is currently sharing a limited profile.") %></span>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -417,9 +417,12 @@ if settings.COURSEWARE_ENABLED:
|
||||
url(r'^courses/{}/lti_rest_endpoints/'.format(settings.COURSE_ID_PATTERN),
|
||||
'courseware.views.get_course_lti_endpoints', name='lti_rest_endpoints'),
|
||||
|
||||
# Student account and profile
|
||||
# Student account
|
||||
url(r'^account/', include('student_account.urls')),
|
||||
|
||||
# Student profile
|
||||
url(r'^u/(?P<username>[\w.@+-]+)$', 'student_profile.views.learner_profile', name='learner_profile'),
|
||||
|
||||
# Student Notes
|
||||
url(r'^courses/{}/edxnotes'.format(settings.COURSE_ID_PATTERN),
|
||||
include('edxnotes.urls'), name="edxnotes_endpoints"),
|
||||
|
||||
Reference in New Issue
Block a user