Consolidate recovery assistance forms

This commit is contained in:
Saleem Latif
2019-01-16 16:26:13 +05:00
parent 33fab4b1e4
commit 46d97caa47
18 changed files with 69 additions and 709 deletions

View File

@@ -1,152 +0,0 @@
(function(define) {
'use strict';
define([
'jquery',
'underscore',
'common/js/spec_helpers/template_helpers',
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'js/student_account/models/AccountRecoveryModel',
'js/student_account/views/AccountRecoveryView'
],
function($, _, TemplateHelpers, AjaxHelpers, AccountRecoveryModel, AccountRecoveryView) {
describe('edx.student.account.AccountRecoveryView', function() {
var model = null,
view = null,
requests = null,
EMAIL = 'xsy@edx.org',
FORM_DESCRIPTION = {
method: 'post',
submit_url: '/account/password',
fields: [{
name: 'email',
label: 'Secondary email',
defaultValue: '',
type: 'text',
required: true,
placeholder: 'place@holder.org',
instructions: 'Enter your secondary email.',
restrictions: {}
}]
};
var createAccountRecoveryView = function(that) {
// Initialize the account recovery model
model = new AccountRecoveryModel({}, {
url: FORM_DESCRIPTION.submit_url,
method: FORM_DESCRIPTION.method
});
// Initialize the account recovery view
view = new AccountRecoveryView({
fields: FORM_DESCRIPTION.fields,
model: model
});
// Spy on AJAX requests
requests = AjaxHelpers.requests(that);
};
var submitEmail = function(validationSuccess) {
// Create a fake click event
var clickEvent = $.Event('click');
// Simulate manual entry of an email address
$('#password-reset-email').val(EMAIL);
// If validationSuccess isn't passed, we avoid
// spying on `view.validate` twice
if (!_.isUndefined(validationSuccess)) {
// Force validation to return as expected
spyOn(view, 'validate').and.returnValue({
isValid: validationSuccess,
message: 'Submission was validated.'
});
}
// Submit the email address
view.submitForm(clickEvent);
};
beforeEach(function() {
setFixtures('<div id="password-reset-form" class="form-wrapper hidden"></div>');
TemplateHelpers.installTemplate('templates/student_account/account_recovery');
TemplateHelpers.installTemplate('templates/student_account/form_field');
});
it('allows the user to request account recovery', function() {
var syncSpy, passwordEmailSentSpy;
createAccountRecoveryView(this);
// We expect these events to be triggered upon a successful account recovery
syncSpy = jasmine.createSpy('syncEvent');
passwordEmailSentSpy = jasmine.createSpy('passwordEmailSentEvent');
view.listenTo(view.model, 'sync', syncSpy);
view.listenTo(view, 'account-recovery-email-sent', passwordEmailSentSpy);
// Submit the form, with successful validation
submitEmail(true);
// Verify that the client contacts the server with the expected data
AjaxHelpers.expectRequest(
requests, 'POST',
FORM_DESCRIPTION.submit_url,
$.param({email: EMAIL})
);
// Respond with status code 200
AjaxHelpers.respondWithJson(requests, {});
// Verify that the events were triggered
expect(syncSpy).toHaveBeenCalled();
expect(passwordEmailSentSpy).toHaveBeenCalled();
// Verify that account recovery view has been removed
expect($(view.el).html().length).toEqual(0);
});
it('validates the email field', function() {
createAccountRecoveryView(this);
// Submit the form, with successful validation
submitEmail(true);
// Verify that validation of the email field occurred
expect(view.validate).toHaveBeenCalledWith($('#password-reset-email')[0]);
// Verify that no submission errors are visible
expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(0);
});
it('displays account recovery validation errors', function() {
createAccountRecoveryView(this);
// Submit the form, with failed validation
submitEmail(false);
// Verify that submission errors are visible
expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(1);
});
it('displays error if the server returns an error while sending account recovery email', function() {
createAccountRecoveryView(this);
submitEmail(true);
// Simulate an error from the LMS servers
AjaxHelpers.respondWithError(requests);
// Expect that an error is displayed
expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(1);
// If we try again and succeed, the error should go away
submitEmail();
// This time, respond with status code 200
AjaxHelpers.respondWithJson(requests, {});
// Expect that the error is hidden
expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(0);
});
});
});
}).call(this, define || RequireJS.define);

View File

@@ -15,13 +15,11 @@
'js/student_account/views/RegisterView',
'js/student_account/views/InstitutionLoginView',
'js/student_account/views/HintedLoginView',
'js/student_account/views/AccountRecoveryView',
'edx-ui-toolkit/js/utils/html-utils',
'js/vendor/history'
],
function($, utility, _, _s, Backbone, LoginModel, PasswordResetModel, RegisterModel, AccountRecoveryModel,
LoginView, PasswordResetView, RegisterView, InstitutionLoginView, HintedLoginView, AccountRecoveryView,
HtmlUtils) {
LoginView, PasswordResetView, RegisterView, InstitutionLoginView, HintedLoginView, HtmlUtils) {
return Backbone.View.extend({
tpl: '#access-tpl',
events: {
@@ -69,7 +67,6 @@
login: options.login_form_desc,
register: options.registration_form_desc,
reset: options.password_reset_form_desc,
account_recovery: options.account_recovery_form_desc,
institution_login: null,
hinted_login: null
};
@@ -125,9 +122,6 @@
if (Backbone.history.getHash() === 'forgot-password-modal') {
this.resetPassword();
}
else if (Backbone.history.getHash() === 'account-recovery-modal') {
this.accountRecovery();
}
this.loadForm(this.activeForm);
},
@@ -163,9 +157,6 @@
// Listen for 'password-help' event to toggle sub-views
this.listenTo(this.subview.login, 'password-help', this.resetPassword);
// Listen for 'account-recovery-help' event to toggle sub-views
this.listenTo(this.subview.login, 'account-recovery-help', this.accountRecovery);
// Listen for 'auth-complete' event so we can enroll/redirect the user appropriately.
this.listenTo(this.subview.login, 'auth-complete', this.authComplete);
},
@@ -186,24 +177,6 @@
$('.password-reset-form').focus();
},
account_recovery: function(data) {
this.accountRecoveryModel.ajaxType = data.method;
this.accountRecoveryModel.urlRoot = data.submit_url;
this.subview.accountRecoveryHelp = new AccountRecoveryView({
fields: data.fields,
model: this.accountRecoveryModel
});
// Listen for 'account-recovery-email-sent' event to toggle sub-views
this.listenTo(
this.subview.accountRecoveryHelp, 'account-recovery-email-sent', this.passwordEmailSent
);
// Focus on the form
$('.password-reset-form').focus();
},
register: function(data) {
var model = new RegisterModel({}, {
method: data.method,
@@ -260,19 +233,6 @@
this.element.scrollTop($('#password-reset-anchor'));
},
accountRecovery: function() {
if (this.isAccountRecoveryFeatureEnabled) {
window.analytics.track('edx.bi.account_recovery.viewed', {
category: 'user-engagement'
});
this.element.hide($(this.el).find('#login-anchor'));
this.loadForm('account_recovery');
this.element.scrollTop($('#password-reset-anchor'));
}
},
toggleForm: function(e) {
var type = $(e.currentTarget).data('type'),
$form = $('#' + type + '-form'),

View File

@@ -1,39 +0,0 @@
(function(define) {
'use strict';
define([
'jquery',
'js/student_account/views/FormView'
],
function($, FormView) {
return FormView.extend({
el: '#password-reset-form',
tpl: '#account_recovery-tpl',
events: {
'click .js-reset': 'submitForm'
},
formType: 'password-reset',
requiredStr: '',
optionalStr: '',
submitButton: '.js-reset',
preRender: function() {
this.element.show($(this.el));
this.element.show($(this.el).parent());
this.listenTo(this.model, 'sync', this.saveSuccess);
},
saveSuccess: function() {
this.trigger('account-recovery-email-sent');
// Destroy the view (but not el) and unbind events
this.$el.empty().off();
this.stopListening();
}
});
});
}).call(this, define || RequireJS.define);

View File

@@ -139,12 +139,6 @@
this.trigger('password-help');
},
accountRecovery: function(event) {
event.preventDefault();
this.trigger('account-recovery-help');
},
getFormData: function() {
var obj = {},
$form = this.$form,

View File

@@ -23,7 +23,6 @@
events: {
'click .js-login': 'submitForm',
'click .forgot-password': 'forgotPassword',
'click .account-recovery': 'accountRecovery',
'click .login-provider': 'thirdPartyAuth'
},
formType: 'login',
@@ -137,13 +136,6 @@
this.clearPasswordResetSuccess();
},
accountRecovery: function(event) {
event.preventDefault();
this.trigger('account-recovery-help');
this.clearPasswordResetSuccess();
},
postFormSubmission: function() {
this.clearPasswordResetSuccess();
},
@@ -152,7 +144,7 @@
var email = $('#password-reset-email').val(),
successTitle = gettext('Check Your Email'),
successMessageHtml = HtmlUtils.interpolateHtml(
gettext('{paragraphStart}You entered {boldStart}{email}{boldEnd}. If this email address is associated with your {platform_name} account, we will send a message with password reset instructions to this email address.{paragraphEnd}' + // eslint-disable-line max-len
gettext('{paragraphStart}You entered {boldStart}{email}{boldEnd}. If this email address is associated with your {platform_name} account, we will send a message with password recovery instructions to this email address.{paragraphEnd}' + // eslint-disable-line max-len
'{paragraphStart}If you do not receive a password reset message, verify that you entered the correct email address, or check your spam folder.{paragraphEnd}' + // eslint-disable-line max-len
'{paragraphStart}If you need further assistance, {anchorStart}contact technical support{anchorEnd}.{paragraphEnd}'), { // eslint-disable-line max-len
boldStart: HtmlUtils.HTML('<b>'),

View File

@@ -787,7 +787,6 @@
'js/spec/student_account/login_spec.js',
'js/spec/student_account/logistration_factory_spec.js',
'js/spec/student_account/password_reset_spec.js',
'js/spec/student_account/account_recovery_spec.js',
'js/spec/student_account/register_spec.js',
'js/spec/student_account/shoppingcart_spec.js',
'js/spec/verify_student/image_input_spec.js',

View File

@@ -1,56 +0,0 @@
## mako
<%page expression_filter="h"/>
<%!
from django.utils.translation import ugettext as _
from openedx.core.djangolib.js_utils import js_escaped_string
from openedx.core.djangolib.markup import HTML, Text
%>
<%inherit file="../main.html"/>
<%namespace name='static' file='../static_content.html'/>
<%block name="title">
<title>${_("Create Your {platform_name} Password").format(platform_name=platform_name)}</title>
</%block>
<%block name="head_extra">
<link type="text/css" rel="stylesheet" href="${STATIC_URL}paragon/static/paragon.min.css">
</%block>
<%block name="bodyclass">view-passwordreset</%block>
<%block name="body">
<div id="password-reset-confirm-container" class="login-register-content login-register">
% if validlink:
${static.renderReact(
component="PasswordResetConfirmation",
id="password-reset-confirm-react",
props={
'csrfToken': csrf_token,
'errorMessage': js_escaped_string(err_msg) if err_msg else '',
'primaryActionButtonLabel': 'Create My Password',
'formTitle': 'Create Your Password',
},
)}
% else:
<div class="status submission-error">
<h4 class="message-title">${_("Invalid Password Create Link")}</h4>
<ul class="message-copy">
${Text(_((
"This password create link is invalid. It may have been used already. "
"To create your password, go to the {start_link}sign-in{end_link} page and "
"select {start_strong}Recovery your account{end_strong}."
))).format(
start_link=HTML('<a href="/login">'),
end_link=HTML('</a>'),
start_strong=HTML('<strong>'),
end_strong=HTML('</strong>')
)
}
</ul>
</div>
% endif
</div>
</%block>

View File

@@ -1,13 +0,0 @@
<div class="js-form-feedback" aria-live="assertive" tabindex="-1">
</div>
<h2><%- gettext("Account Recovery") %></h2>
<form id="account-recovery" class="account-recovery-form password-reset-form" tabindex="-1" method="POST">
<p class="action-label"><%- gettext("Please enter your secondary email address below and we will send you instructions for recovering your account and setting a new password.") %></p>
<%= HtmlUtils.HTML(fields) %>
<button type="submit" class="action action-primary action-update js-reset"><%- gettext("Recover my account") %></button>
</form>

View File

@@ -133,6 +133,6 @@
<% } %>
<% if( form === 'login' && name === 'password' ) { %>
<button type="button" class="forgot-password field-link"><%- gettext("Forgot password?") %></button>
<button type="button" class="forgot-password field-link"><%- gettext("Need help logging in?") %></button>
<% } %>
</div>

View File

@@ -30,7 +30,7 @@
</%block>
<%block name="header_extras">
% for template_name in ["account", "access", "form_field", "login", "register", "institution_login", "institution_register", "password_reset", "account_recovery", "hinted_login"]:
% for template_name in ["account", "access", "form_field", "login", "register", "institution_login", "institution_register", "password_reset", "hinted_login"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="student_account/${template_name}.underscore" />
</script>

View File

@@ -5,9 +5,9 @@
<form id="password-reset" class="password-reset-form" tabindex="-1" method="POST">
<p class="action-label"><%- gettext("Please enter your email address below and we will send you instructions for setting a new password.") %></p>
<p class="action-label"><%- gettext("Please enter your registration or recovery email address below and we will send you an email with instructions.") %></p>
<%= fields %>
<%= HtmlUtils.HTML(fields) %>
<button type="submit" class="action action-primary action-update js-reset"><%- gettext("Reset my password") %></button>
<button type="submit" class="action action-primary action-update js-reset"><%- gettext("Recover my password") %></button>
</form>