diff --git a/common/test/acceptance/pages/lms/fields.py b/common/test/acceptance/pages/lms/fields.py index 7512937b84..54d48dbd1e 100644 --- a/common/test/acceptance/pages/lms/fields.py +++ b/common/test/acceptance/pages/lms/fields.py @@ -182,7 +182,7 @@ class FieldsMixin(object): """ self.wait_for_field(field_id) - return self.q(css='.u-field-{} .u-field-value'.format(field_id)).text[0] + return self.q(css='.u-field-{} .u-field-value .u-field-value-readonly'.format(field_id)).text[0] def value_for_dropdown_field(self, field_id, value=None): """ @@ -210,7 +210,7 @@ class FieldsMixin(object): """ self.wait_for_field(field_id) - query = self.q(css='.u-field-{} a'.format(field_id)) + query = self.q(css='.u-field-link-title-{}'.format(field_id)) return query.text[0] if query.present else None def click_on_link_in_link_field(self, field_id): diff --git a/lms/static/js/spec/student_account/account_settings_factory_spec.js b/lms/static/js/spec/student_account/account_settings_factory_spec.js index 113a44e814..9f2d8008e7 100644 --- a/lms/static/js/spec/student_account/account_settings_factory_spec.js +++ b/lms/static/js/spec/student_account/account_settings_factory_spec.js @@ -32,13 +32,13 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j var AUTH_DATA = { 'providers': [ { - 'name': "Network 1", + 'name': "Network1", 'connected': true, 'connect_url': 'yetanother1.com/auth/connect', 'disconnect_url': 'yetanother1.com/auth/disconnect' }, { - 'name': "Network 2", + 'name': "Network2", 'connected': true, 'connect_url': 'yetanother2.com/auth/connect', 'disconnect_url': 'yetanother2.com/auth/disconnect' diff --git a/lms/static/js/spec/student_account/account_settings_fields_helpers.js b/lms/static/js/spec/student_account/account_settings_fields_helpers.js index 18ff5d1ab0..0ee2fa9749 100644 --- a/lms/static/js/spec/student_account/account_settings_fields_helpers.js +++ b/lms/static/js/spec/student_account/account_settings_fields_helpers.js @@ -5,11 +5,11 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j 'use strict'; var verifyAuthField = function (view, data, requests) { - var selector = '.u-field-value > a'; + var selector = '.u-field-value .u-field-link-title-' + view.options.valueAttribute; spyOn(view, 'redirect_to'); - FieldViewsSpecHelpers.expectTitleAndMessageToBe(view, data.title, data.helpMessage); + FieldViewsSpecHelpers.expectTitleAndMessageToContain(view, data.title, data.helpMessage); expect(view.$(selector).text().trim()).toBe('Unlink'); view.$(selector).click(); FieldViewsSpecHelpers.expectMessageContains(view, 'Unlinking'); diff --git a/lms/static/js/spec/student_profile/helpers.js b/lms/static/js/spec/student_profile/helpers.js index dff6e9c26c..b5c2602d99 100644 --- a/lms/static/js/spec/student_profile/helpers.js +++ b/lms/static/js/spec/student_profile/helpers.js @@ -18,10 +18,10 @@ define(['underscore'], function(_) { expect(view.fieldValue()).toBe(view.modelValue()); } else if ('optionForValue' in view) { - expect($($element.find('.u-field-value')[0]).text()).toBe(view.displayValue(view.modelValue())); + expect($($element.find('.u-field-value .u-field-value-readonly')[0]).text()).toBe(view.displayValue(view.modelValue())); }else { - expect($($element.find('.u-field-value')[0]).text()).toBe(view.modelValue()); + expect($($element.find('.u-field-value .u-field-value-readonly')[0]).text()).toBe(view.modelValue()); } } else { throw new Error('Unexpected field type: ' + view.fieldType); diff --git a/lms/static/js/spec/views/fields_helpers.js b/lms/static/js/spec/views/fields_helpers.js index e7899357ef..6898ffa067 100644 --- a/lms/static/js/spec/views/fields_helpers.js +++ b/lms/static/js/spec/views/fields_helpers.js @@ -59,19 +59,19 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j }; }; - 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); + var expectTitleToContain = function(view, expectedTitle) { + expect(view.$('.u-field-title').text().trim()).toContain(expectedTitle); }; var expectMessageContains = function(view, expectedText) { expect(view.$('.u-field-message').html()).toContain(expectedText); }; + var expectTitleAndMessageToContain = function(view, expectedTitle, expectedMessage) { + expectTitleToContain(view, expectedTitle); + expectMessageContains(view, expectedMessage); + }; + var expectAjaxRequestWithData = function(requests, data) { AjaxHelpers.expectJsonRequest( requests, 'PATCH', API_URL, data @@ -82,7 +82,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j var message = 'Here to help!'; - view.message(message); + view.showHelpMessage(message); expectMessageContains(view, message); view.showHelpMessage(); @@ -121,7 +121,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j view.showSuccessMessage(); expectMessageContains(view, view.indicators.success); // But if we change the message, it should not get reset. - view.message("Do not reset this!"); + view.showHelpMessage("Do not reset this!"); jasmine.Clock.tick(5000); expectMessageContains(view, "Do not reset this!"); }; @@ -132,15 +132,15 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j if (data.editable === 'toggle') { expect(view.el).toHaveClass('mode-placeholder'); - expectTitleToBe(view, data.title); + expectTitleToContain(view, data.title); expectMessageContains(view, view.indicators.canEdit); view.$el.click(); } else { - expectTitleAndMessageToBe(view, data.title, data.helpMessage); + expectTitleAndMessageToContain(view, data.title, data.helpMessage, false); } expect(view.el).toHaveClass('mode-edit'); - expect(view.fieldValue()).not.toBe(data.validValue); + expect(view.fieldValue()).not.toContain(data.validValue); view.$(data.valueInputSelector).val(data.validValue).change(); // When the value in the field is changed @@ -220,8 +220,8 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j UserAccountModel: UserAccountModel, createFieldData: createFieldData, createErrorMessage: createErrorMessage, - expectTitleToBe: expectTitleToBe, - expectTitleAndMessageToBe: expectTitleAndMessageToBe, + expectTitleToContain: expectTitleToContain, + expectTitleAndMessageToContain: expectTitleAndMessageToContain, expectMessageContains: expectMessageContains, expectAjaxRequestWithData: expectAjaxRequestWithData, verifyMessageUpdates: verifyMessageUpdates, diff --git a/lms/static/js/spec/views/fields_spec.js b/lms/static/js/spec/views/fields_spec.js index 0a89b3cad5..4c2c249071 100644 --- a/lms/static/js/spec/views/fields_spec.js +++ b/lms/static/js/spec/views/fields_spec.js @@ -96,7 +96,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j }); var view = new FieldViews.ReadonlyFieldView(fieldData).render(); - FieldViewsSpecHelpers.expectTitleAndMessageToBe(view, fieldData.title, fieldData.helpMessage); + FieldViewsSpecHelpers.expectTitleAndMessageToContain(view, fieldData.title, fieldData.helpMessage, false); expect(view.$('.u-field-value input').val().trim()).toBe(USERNAME); view.model.set({'username': 'bookworm'}); @@ -137,7 +137,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j }); var view = new FieldViews.DropdownFieldView(fieldData).render(); - FieldViewsSpecHelpers.expectTitleAndMessageToBe(view, fieldData.title, fieldData.helpMessage); + FieldViewsSpecHelpers.expectTitleAndMessageToContain(view, fieldData.title, fieldData.helpMessage, false); expect(view.el).toHaveClass('mode-hidden'); view.model.set({'name': fieldData.options[1][0]}); @@ -205,14 +205,14 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j // 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); + FieldViewsSpecHelpers.expectTitleAndMessageToContain(view, fieldData.title, fieldData.helpMessage, false); expect(view.el).toHaveClass('mode-hidden'); - expect(view.$('.u-field-value').text()).toBe(fieldData.placeholderValue); + expect(view.$('.u-field-value .u-field-value-readonly').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); + expect(view.$('.u-field-value .u-field-value-readonly').text()).toBe(bio); view.$el.click(); expect(view.el).toHaveClass('mode-display'); }); @@ -235,10 +235,10 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j var view = new FieldViews.TextareaFieldView(fieldData).render(); - FieldViewsSpecHelpers.expectTitleToBe(view, fieldData.title); + FieldViewsSpecHelpers.expectTitleToContain(view, fieldData.title); FieldViewsSpecHelpers.expectMessageContains(view, view.indicators.canEdit); expect(view.el).toHaveClass('mode-placeholder'); - expect(view.$('.u-field-value').text()).toBe(fieldData.placeholderValue); + expect(view.$('.u-field-value .u-field-value-readonly').text()).toBe(fieldData.placeholderValue); view.$('.wrapper-u-field').click(); expect(view.el).toHaveClass('mode-edit'); @@ -254,19 +254,20 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j view.$(valueInputSelector).val('').focusout(); AjaxHelpers.respondWithNoContent(requests); expect(view.el).toHaveClass('mode-placeholder'); - expect(view.$('.u-field-value').text()).toBe(fieldData.placeholderValue); + expect(view.$('.u-field-value .u-field-value-readonly').text()).toBe(fieldData.placeholderValue); }); it("correctly renders LinkFieldView", function() { var fieldData = FieldViewsSpecHelpers.createFieldData(FieldViews.LinkFieldView, { title: 'Title', linkTitle: 'Link title', - helpMessage: 'Click the link.' + helpMessage: 'Click the link.', + valueAttribute: 'password-reset' }); var view = new FieldViews.LinkFieldView(fieldData).render(); - FieldViewsSpecHelpers.expectTitleAndMessageToBe(view, fieldData.title, fieldData.helpMessage); - expect(view.$('.u-field-value > a').text().trim()).toBe(fieldData.linkTitle); + FieldViewsSpecHelpers.expectTitleAndMessageToContain(view, fieldData.title, fieldData.helpMessage, false); + expect(view.$('.u-field-value > a .u-field-link-title-' + view.options.valueAttribute).text().trim()).toBe(fieldData.linkTitle); }); }); }); diff --git a/lms/static/js/student_account/views/account_settings_factory.js b/lms/static/js/student_account/views/account_settings_factory.js index c9a1610d3d..b92f87e402 100644 --- a/lms/static/js/student_account/views/account_settings_factory.js +++ b/lms/static/js/student_account/views/account_settings_factory.js @@ -58,10 +58,11 @@ view: new AccountSettingsFieldViews.PasswordFieldView({ model: userAccountModel, title: gettext('Password'), + screenReaderTitle: gettext('Reset your Password'), valueAttribute: 'password', emailAttribute: 'email', linkTitle: gettext('Reset Password'), - linkHref: fieldsData.password.url, + linkHref: fieldsData['password']['url'], helpMessage: gettext( 'When you click "Reset Password", a message will be sent to your email address. ' + 'Click the link in the message to reset your password.' @@ -139,6 +140,9 @@ return { 'view': new AccountSettingsFieldViews.AuthFieldView({ title: provider.name, + screenReaderTitle: interpolate_text( + gettext("Connect your {accountName} account"), {accountName: provider['name']} + ), valueAttribute: 'auth-' + provider.name.toLowerCase(), helpMessage: '', connected: provider.connected, diff --git a/lms/static/js/student_account/views/account_settings_fields.js b/lms/static/js/student_account/views/account_settings_fields.js index 16746a4c8e..159ad4059b 100644 --- a/lms/static/js/student_account/views/account_settings_fields.js +++ b/lms/static/js/student_account/views/account_settings_fields.js @@ -36,7 +36,7 @@ view.showSuccessMessage(); }, error: function () { - view.message( + view.showNotificationMessage( view.indicators.error + gettext( 'You must sign out of edX and sign back in before your language ' + @@ -121,6 +121,7 @@ this.$el.html(this.template({ id: this.options.valueAttribute, title: this.options.title, + screenReaderTitle: this.options.screenReaderTitle, linkTitle: this.options.connected ? gettext('Unlink') : gettext('Link'), linkHref: '', message: this.helpMessage diff --git a/lms/static/js/student_profile/views/learner_profile_factory.js b/lms/static/js/student_profile/views/learner_profile_factory.js index 95bacf204e..afeb198a1b 100644 --- a/lms/static/js/student_profile/views/learner_profile_factory.js +++ b/lms/static/js/student_profile/views/learner_profile_factory.js @@ -65,6 +65,7 @@ var sectionOneFieldViews = [ new FieldsView.DropdownFieldView({ model: accountSettingsModel, + screenReaderTitle: gettext('Location'), required: true, editable: editable, showMessages: false, @@ -76,6 +77,7 @@ }), new AccountSettingsFieldViews.LanguageProficienciesFieldView({ model: accountSettingsModel, + screenReaderTitle: gettext('Preferred Language'), required: false, editable: editable, showMessages: false, diff --git a/lms/static/js/student_profile/views/learner_profile_fields.js b/lms/static/js/student_profile/views/learner_profile_fields.js index 071fca5fe6..9574e97a35 100644 --- a/lms/static/js/student_profile/views/learner_profile_fields.js +++ b/lms/static/js/student_profile/views/learner_profile_fields.js @@ -10,12 +10,12 @@ render: function () { this._super(); - this.message(); + this.showNotificationMessage(); this.updateFieldValue(); return this; }, - message: function () { + showNotificationMessage: function () { var accountSettingsLink = '' + gettext('Account Settings page.') + ''; if (this.profileIsPrivate) { this._super(interpolate_text( diff --git a/lms/static/js/views/fields.js b/lms/static/js/views/fields.js index f237d01abe..f6c1fd3516 100644 --- a/lms/static/js/views/fields.js +++ b/lms/static/js/views/fields.js @@ -20,12 +20,12 @@ tagName: 'div', indicators: { - 'canEdit': '', - 'error': '', - 'validationError': '', - 'inProgress': '', - 'success': '', - 'plus': '' + 'canEdit': 'gettext("Editable")', + 'error': 'gettext("Error")', + 'validationError': 'gettext("Validation Error")', + 'inProgress': 'gettext("In Progress")', + 'success': 'gettext("Success")', + 'plus': 'gettext("Placeholder")' }, messages: { @@ -36,15 +36,17 @@ 'success': gettext('Your changes have been saved.') }, - initialize: function (options) { + initialize: function () { this.template = _.template($(this.templateSelector).text()); this.helpMessage = this.options.helpMessage || ''; this.showMessages = _.isUndefined(this.options.showMessages) ? true : this.options.showMessages; - _.bindAll(this, 'modelValue', 'modelValueIsSet', 'message', 'getMessage', 'title', - 'showHelpMessage', 'showInProgressMessage', 'showSuccessMessage', 'showErrorMessage'); + _.bindAll(this, 'modelValue', 'modelValueIsSet', 'showNotificationMessage','getNotificationMessage', + 'getMessage', 'title', 'showHelpMessage', 'showInProgressMessage', 'showSuccessMessage', + 'showErrorMessage' + ); }, modelValue: function () { @@ -55,10 +57,6 @@ return (this.modelValue() === true); }, - message: function (message) { - return this.$('.u-field-message').html(message); - }, - title: function (text) { return this.$('.u-field-title').html(text); }, @@ -72,25 +70,38 @@ return this.indicators[message_status]; }, + showHelpMessage: function (message) { + if (_.isUndefined(message) || _.isNull(message)) { + message = this.helpMessage; + } + this.$('.u-field-message-notification').html(''); + this.$('.u-field-message-help').html(message); + }, + + getNotificationMessage: function() { + return this.$('.u-field-message-notification').html(); + }, + + showNotificationMessage: function(message) { + this.$('.u-field-message-help').html(''); + this.$('.u-field-message-notification').html(message); + }, + showCanEditMessage: function(show) { if (!_.isUndefined(show) && show) { - this.message(this.getMessage('canEdit')); + this.showNotificationMessage(this.getMessage('canEdit')); } else { - this.message(''); + this.showNotificationMessage(''); } }, - showHelpMessage: function () { - this.message(this.helpMessage); - }, - showInProgressMessage: function () { - this.message(this.getMessage('inProgress')); + this.showNotificationMessage(this.getMessage('inProgress')); }, showSuccessMessage: function () { var successMessage = this.getMessage('success'); - this.message(successMessage); + this.showNotificationMessage(successMessage); if (this.options.refreshPageOnSave) { document.location.reload(); @@ -102,7 +113,7 @@ this.lastSuccessMessageContext = context; setTimeout(function () { - if ((context === view.lastSuccessMessageContext) && (view.message().html() === successMessage)) { + if ((context === view.lastSuccessMessageContext) && (view.getNotificationMessage() === successMessage)) { view.showHelpMessage(); } }, messageRevertDelay); @@ -116,12 +127,12 @@ errors.field_errors[this.options.valueAttribute].user_message ), message = this.indicators.validationError + validationErrorMessage; - this.message(message); + this.showNotificationMessage(message); } catch (error) { - this.message(this.getMessage('error')); + this.showNotificationMessage(this.getMessage('error')); } } else { - this.message(this.getMessage('error')); + this.showNotificationMessage(this.getMessage('error')); } } }); @@ -305,6 +316,7 @@ id: this.options.valueAttribute, mode: this.mode, title: this.options.title, + screenReaderTitle: this.options.screenReaderTitle || this.options.title, iconName: this.options.iconName, required: this.options.required, selectOptions: this.options.options, @@ -351,7 +363,8 @@ if (this.modelValueIsSet() === false) { value = this.options.placeholderValue || ''; } - this.$('.u-field-value').html(Mustache.escapeHtml(value)); + this.$('.u-field-value').attr('aria-label', this.options.title); + this.$('.u-field-value-readonly').html(Mustache.escapeHtml(value)); this.showDisplayMode(false); } else { this.$('.u-field-value select').val(this.modelValue() || ''); @@ -413,9 +426,11 @@ } this.$el.html(this.template({ id: this.options.valueAttribute, + screenReaderTitle: this.options.screenReaderTitle || this.options.title, mode: this.mode, value: value, - message: this.helpMessage + message: this.helpMessage, + placeholderValue: this.options.placeholderValue })); this.delegateEvents(); this.title((this.modelValue() || this.mode === 'edit') ? this.options.title : this.indicators['plus'] + this.options.title); @@ -489,6 +504,7 @@ this.$el.html(this.template({ id: this.options.valueAttribute, title: this.options.title, + screenReaderTitle: this.options.screenReaderTitle || this.options.title, linkTitle: this.options.linkTitle, linkHref: this.options.linkHref, message: this.helpMessage diff --git a/lms/static/sass/shared/_fields.scss b/lms/static/sass/shared/_fields.scss index a2ecf6898f..f277e96114 100644 --- a/lms/static/sass/shared/_fields.scss +++ b/lms/static/sass/shared/_fields.scss @@ -87,8 +87,9 @@ color: $gray; vertical-align: top; margin-bottom: 0; + -webkit-font-smoothing: antialiased; - label { + label, span { @include margin-left($baseline/2); } } @@ -114,4 +115,9 @@ i { @include margin-right($baseline/4); } + + .u-field-message-help, + .u-field-message-notification { + color: $gray-l1; + } } diff --git a/lms/static/sass/views/_account-settings.scss b/lms/static/sass/views/_account-settings.scss index 7b204d4cfc..a5d2f7b2f0 100644 --- a/lms/static/sass/views/_account-settings.scss +++ b/lms/static/sass/views/_account-settings.scss @@ -66,4 +66,15 @@ box-shadow: 0 0 1px 1px $shadow-l2; border-radius: 5px; } + + a span { + color: $link-color; + } + + a span { + &:hover, &:focus { + color: $pink; + text-decoration: none !important; + } + } } diff --git a/lms/templates/fields/field_dropdown.underscore b/lms/templates/fields/field_dropdown.underscore index 25505cdd22..abc5705acf 100644 --- a/lms/templates/fields/field_dropdown.underscore +++ b/lms/templates/fields/field_dropdown.underscore @@ -5,12 +5,12 @@ <% } %> <% if (iconName) { %> - + <% } %> <% if (mode === 'edit') { %> - diff --git a/lms/templates/fields/field_link.underscore b/lms/templates/fields/field_link.underscore index fbdfad2bea..f17276a1dc 100644 --- a/lms/templates/fields/field_link.underscore +++ b/lms/templates/fields/field_link.underscore @@ -1,11 +1,13 @@ - + - - <%- gettext(linkTitle) %> + + <%- gettext(screenReaderTitle) %> + diff --git a/lms/templates/fields/field_readonly.underscore b/lms/templates/fields/field_readonly.underscore index c8aa651778..a97e4a47b8 100644 --- a/lms/templates/fields/field_readonly.underscore +++ b/lms/templates/fields/field_readonly.underscore @@ -5,5 +5,6 @@ diff --git a/lms/templates/fields/field_text.underscore b/lms/templates/fields/field_text.underscore index dd876f31cb..e333dbcada 100644 --- a/lms/templates/fields/field_text.underscore +++ b/lms/templates/fields/field_text.underscore @@ -2,8 +2,9 @@ <%- gettext(title) %> - + diff --git a/lms/templates/fields/field_textarea.underscore b/lms/templates/fields/field_textarea.underscore index 0f2f482d98..d96ff2d87e 100644 --- a/lms/templates/fields/field_textarea.underscore +++ b/lms/templates/fields/field_textarea.underscore @@ -1,14 +1,18 @@