New AB Testing URL for checkout page.
ECOM-2866
This commit is contained in:
@@ -102,11 +102,17 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
result = self.client.login(username=self.USERNAME, password=self.PASSWORD)
|
||||
self.assertTrue(result, msg="Could not log in")
|
||||
|
||||
@ddt.data("verified", "professional")
|
||||
def test_start_flow_not_verified(self, course_mode):
|
||||
@ddt.data(
|
||||
("verified", "verify_student_start_flow"),
|
||||
("professional", "verify_student_start_flow"),
|
||||
("verified", "verify_student_begin_flow"),
|
||||
("professional", "verify_student_begin_flow")
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_start_flow_not_verified(self, course_mode, payment_flow):
|
||||
course = self._create_course(course_mode)
|
||||
self._enroll(course.id)
|
||||
response = self._get_page('verify_student_start_flow', course.id)
|
||||
response = self._get_page(payment_flow, course.id)
|
||||
self._assert_displayed_mode(response, course_mode)
|
||||
self._assert_steps_displayed(
|
||||
response,
|
||||
@@ -120,11 +126,15 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
])
|
||||
self._assert_upgrade_session_flag(False)
|
||||
|
||||
@ddt.data("no-id-professional")
|
||||
def test_start_flow_with_no_id_professional(self, course_mode):
|
||||
@ddt.data(
|
||||
("no-id-professional", "verify_student_start_flow"),
|
||||
("no-id-professional", "verify_student_begin_flow")
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_start_flow_with_no_id_professional(self, course_mode, payment_flow):
|
||||
course = self._create_course(course_mode)
|
||||
self._enroll(course.id)
|
||||
response = self._get_page('verify_student_start_flow', course.id)
|
||||
response = self._get_page(payment_flow, course.id)
|
||||
self._assert_displayed_mode(response, course_mode)
|
||||
self._assert_steps_displayed(
|
||||
response,
|
||||
@@ -134,12 +144,26 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
self._assert_messaging(response, PayAndVerifyView.FIRST_TIME_VERIFY_MSG)
|
||||
self._assert_requirements_displayed(response, [])
|
||||
|
||||
@ddt.data("expired", "denied")
|
||||
def test_start_flow_expired_or_denied_verification(self, verification_status):
|
||||
def test_ab_testing_page(self):
|
||||
course = self._create_course("verified")
|
||||
self._enroll(course.id, "verified")
|
||||
response = self._get_page("verify_student_begin_flow", course.id)
|
||||
self._assert_displayed_mode(response, "verified")
|
||||
self.assertContains(response, "Upgrade to a Verified Certificate")
|
||||
self.assertContains(response, "Before you upgrade to a certificate track,")
|
||||
self.assertContains(response, "To receive a certificate, you must also verify your identity")
|
||||
self.assertContains(response, "You will use your webcam to take a picture of")
|
||||
|
||||
@ddt.data(
|
||||
("expired", "verify_student_start_flow"),
|
||||
("denied", "verify_student_begin_flow")
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_start_flow_expired_or_denied_verification(self, verification_status, payment_flow):
|
||||
course = self._create_course("verified")
|
||||
self._enroll(course.id, "verified")
|
||||
self._set_verification_status(verification_status)
|
||||
response = self._get_page('verify_student_start_flow', course.id)
|
||||
response = self._get_page(payment_flow, course.id)
|
||||
|
||||
# Expect the same content as when the user has not verified
|
||||
self._assert_steps_displayed(
|
||||
@@ -154,18 +178,24 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
])
|
||||
|
||||
@ddt.data(
|
||||
("verified", "submitted"),
|
||||
("verified", "approved"),
|
||||
("verified", "error"),
|
||||
("professional", "submitted"),
|
||||
("no-id-professional", None),
|
||||
("verified", "submitted", "verify_student_start_flow"),
|
||||
("verified", "approved", "verify_student_start_flow"),
|
||||
("verified", "error", "verify_student_start_flow"),
|
||||
("professional", "submitted", "verify_student_start_flow"),
|
||||
("no-id-professional", None, "verify_student_start_flow"),
|
||||
("verified", "submitted", "verify_student_begin_flow"),
|
||||
("verified", "approved", "verify_student_begin_flow"),
|
||||
("verified", "error", "verify_student_begin_flow"),
|
||||
("professional", "submitted", "verify_student_begin_flow"),
|
||||
("no-id-professional", None, "verify_student_begin_flow"),
|
||||
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_start_flow_already_verified(self, course_mode, verification_status):
|
||||
def test_start_flow_already_verified(self, course_mode, verification_status, payment_flow):
|
||||
course = self._create_course(course_mode)
|
||||
self._enroll(course.id)
|
||||
self._set_verification_status(verification_status)
|
||||
response = self._get_page('verify_student_start_flow', course.id)
|
||||
response = self._get_page(payment_flow, course.id)
|
||||
self._assert_displayed_mode(response, course_mode)
|
||||
self._assert_steps_displayed(
|
||||
response,
|
||||
@@ -175,11 +205,17 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
self._assert_messaging(response, PayAndVerifyView.FIRST_TIME_VERIFY_MSG)
|
||||
self._assert_requirements_displayed(response, [])
|
||||
|
||||
@ddt.data("verified", "professional")
|
||||
def test_start_flow_already_paid(self, course_mode):
|
||||
@ddt.data(
|
||||
("verified", "verify_student_start_flow"),
|
||||
("professional", "verify_student_start_flow"),
|
||||
("verified", "verify_student_begin_flow"),
|
||||
("professional", "verify_student_begin_flow")
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_start_flow_already_paid(self, course_mode, payment_flow):
|
||||
course = self._create_course(course_mode)
|
||||
self._enroll(course.id, course_mode)
|
||||
response = self._get_page('verify_student_start_flow', course.id)
|
||||
response = self._get_page(payment_flow, course.id)
|
||||
self._assert_displayed_mode(response, course_mode)
|
||||
self._assert_steps_displayed(
|
||||
response,
|
||||
@@ -192,15 +228,16 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
PayAndVerifyView.WEBCAM_REQ,
|
||||
])
|
||||
|
||||
def test_start_flow_not_enrolled(self):
|
||||
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
||||
def test_start_flow_not_enrolled(self, payment_flow):
|
||||
course = self._create_course("verified")
|
||||
self._set_verification_status("submitted")
|
||||
response = self._get_page('verify_student_start_flow', course.id)
|
||||
response = self._get_page(payment_flow, course.id)
|
||||
|
||||
# This shouldn't happen if the student has been auto-enrolled,
|
||||
# but if they somehow end up on this page without enrolling,
|
||||
# treat them as if they need to pay
|
||||
response = self._get_page('verify_student_start_flow', course.id)
|
||||
response = self._get_page(payment_flow, course.id)
|
||||
self._assert_steps_displayed(
|
||||
response,
|
||||
PayAndVerifyView.PAYMENT_STEPS,
|
||||
@@ -208,7 +245,8 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
)
|
||||
self._assert_requirements_displayed(response, [])
|
||||
|
||||
def test_start_flow_unenrolled(self):
|
||||
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
||||
def test_start_flow_unenrolled(self, payment_flow):
|
||||
course = self._create_course("verified")
|
||||
self._set_verification_status("submitted")
|
||||
self._enroll(course.id, "verified")
|
||||
@@ -216,7 +254,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
|
||||
# If unenrolled, treat them like they haven't paid at all
|
||||
# (we assume that they've gotten a refund or didn't pay initially)
|
||||
response = self._get_page('verify_student_start_flow', course.id)
|
||||
response = self._get_page(payment_flow, course.id)
|
||||
self._assert_steps_displayed(
|
||||
response,
|
||||
PayAndVerifyView.PAYMENT_STEPS,
|
||||
@@ -225,27 +263,31 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
self._assert_requirements_displayed(response, [])
|
||||
|
||||
@ddt.data(
|
||||
("verified", "submitted"),
|
||||
("verified", "approved"),
|
||||
("professional", "submitted")
|
||||
("verified", "submitted", "verify_student_start_flow"),
|
||||
("verified", "approved", "verify_student_start_flow"),
|
||||
("professional", "submitted", "verify_student_start_flow"),
|
||||
("verified", "submitted", "verify_student_begin_flow"),
|
||||
("verified", "approved", "verify_student_begin_flow"),
|
||||
("professional", "submitted", "verify_student_begin_flow")
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_start_flow_already_verified_and_paid(self, course_mode, verification_status):
|
||||
def test_start_flow_already_verified_and_paid(self, course_mode, verification_status, payment_flow):
|
||||
course = self._create_course(course_mode)
|
||||
self._enroll(course.id, course_mode)
|
||||
self._set_verification_status(verification_status)
|
||||
response = self._get_page(
|
||||
'verify_student_start_flow',
|
||||
payment_flow,
|
||||
course.id,
|
||||
expected_status_code=302
|
||||
)
|
||||
self._assert_redirects_to_dashboard(response)
|
||||
|
||||
@patch.dict(settings.FEATURES, {"IS_EDX_DOMAIN": True})
|
||||
def test_pay_and_verify_hides_header_nav(self):
|
||||
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
||||
def test_pay_and_verify_hides_header_nav(self, payment_flow):
|
||||
course = self._create_course("verified")
|
||||
self._enroll(course.id, "verified")
|
||||
response = self._get_page('verify_student_start_flow', course.id)
|
||||
response = self._get_page(payment_flow, course.id)
|
||||
|
||||
# Verify that the header navigation links are hidden for the edx.org version
|
||||
self.assertNotContains(response, "How it Works")
|
||||
@@ -351,7 +393,8 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
PayAndVerifyView.WEBCAM_REQ,
|
||||
])
|
||||
|
||||
def test_payment_cannot_skip(self):
|
||||
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
||||
def test_payment_cannot_skip(self, payment_flow):
|
||||
"""
|
||||
Simple test to verify that certain steps cannot be skipped. This test sets up
|
||||
a scenario where the user should be on the MAKE_PAYMENT_STEP, but is trying to
|
||||
@@ -360,7 +403,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
"""
|
||||
course = self._create_course("verified")
|
||||
response = self._get_page(
|
||||
'verify_student_start_flow',
|
||||
payment_flow,
|
||||
course.id,
|
||||
skip_first_step=True
|
||||
)
|
||||
@@ -523,6 +566,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
|
||||
pages = [
|
||||
'verify_student_start_flow',
|
||||
'verify_student_begin_flow',
|
||||
'verify_student_verify_now',
|
||||
'verify_student_upgrade_and_verify',
|
||||
]
|
||||
@@ -534,16 +578,25 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
expected_status_code=404
|
||||
)
|
||||
|
||||
@ddt.data([], ["no-id-professional", "professional"], ["honor", "audit"])
|
||||
def test_no_id_professional_entry_point(self, modes_available):
|
||||
@ddt.data(
|
||||
([], "verify_student_start_flow"),
|
||||
(["no-id-professional", "professional"], "verify_student_start_flow"),
|
||||
(["honor", "audit"], "verify_student_start_flow"),
|
||||
([], "verify_student_begin_flow"),
|
||||
(["no-id-professional", "professional"], "verify_student_begin_flow"),
|
||||
(["honor", "audit"], "verify_student_begin_flow"),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_no_id_professional_entry_point(self, modes_available, payment_flow):
|
||||
course = self._create_course(*modes_available)
|
||||
if "no-id-professional" in modes_available or "professional" in modes_available:
|
||||
self._get_page("verify_student_start_flow", course.id, expected_status_code=200)
|
||||
self._get_page(payment_flow, course.id, expected_status_code=200)
|
||||
else:
|
||||
self._get_page("verify_student_start_flow", course.id, expected_status_code=404)
|
||||
self._get_page(payment_flow, course.id, expected_status_code=404)
|
||||
|
||||
@ddt.data(
|
||||
"verify_student_start_flow",
|
||||
"verify_student_begin_flow",
|
||||
"verify_student_verify_now",
|
||||
"verify_student_upgrade_and_verify",
|
||||
)
|
||||
@@ -561,6 +614,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
|
||||
@ddt.data(
|
||||
"verify_student_start_flow",
|
||||
"verify_student_begin_flow",
|
||||
"verify_student_verify_now",
|
||||
"verify_student_upgrade_and_verify",
|
||||
)
|
||||
@@ -572,11 +626,12 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
expected_status_code=404
|
||||
)
|
||||
|
||||
def test_account_not_active(self):
|
||||
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
||||
def test_account_not_active(self, payment_flow):
|
||||
self.user.is_active = False
|
||||
self.user.save()
|
||||
course = self._create_course("verified")
|
||||
response = self._get_page('verify_student_start_flow', course.id)
|
||||
response = self._get_page(payment_flow, course.id)
|
||||
self._assert_steps_displayed(
|
||||
response,
|
||||
PayAndVerifyView.PAYMENT_STEPS + PayAndVerifyView.VERIFICATION_STEPS,
|
||||
@@ -588,32 +643,36 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
PayAndVerifyView.WEBCAM_REQ,
|
||||
])
|
||||
|
||||
def test_no_contribution(self):
|
||||
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
||||
def test_no_contribution(self, payment_flow):
|
||||
# Do NOT specify a contribution for the course in a session var.
|
||||
course = self._create_course("verified")
|
||||
response = self._get_page("verify_student_start_flow", course.id)
|
||||
response = self._get_page(payment_flow, course.id)
|
||||
self._assert_contribution_amount(response, "")
|
||||
|
||||
def test_contribution_other_course(self):
|
||||
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
||||
def test_contribution_other_course(self, payment_flow):
|
||||
# Specify a contribution amount for another course in the session
|
||||
course = self._create_course("verified")
|
||||
other_course_id = CourseLocator(org="other", run="test", course="test")
|
||||
self._set_contribution("12.34", other_course_id)
|
||||
|
||||
# Expect that the contribution amount is NOT pre-filled,
|
||||
response = self._get_page("verify_student_start_flow", course.id)
|
||||
response = self._get_page(payment_flow, course.id)
|
||||
self._assert_contribution_amount(response, "")
|
||||
|
||||
def test_contribution(self):
|
||||
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
||||
def test_contribution(self, payment_flow):
|
||||
# Specify a contribution amount for this course in the session
|
||||
course = self._create_course("verified")
|
||||
self._set_contribution("12.34", course.id)
|
||||
|
||||
# Expect that the contribution amount is pre-filled,
|
||||
response = self._get_page("verify_student_start_flow", course.id)
|
||||
response = self._get_page(payment_flow, course.id)
|
||||
self._assert_contribution_amount(response, "12.34")
|
||||
|
||||
def test_verification_deadline(self):
|
||||
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
||||
def test_verification_deadline(self, payment_flow):
|
||||
deadline = datetime(2999, 1, 2, tzinfo=pytz.UTC)
|
||||
course = self._create_course("verified")
|
||||
|
||||
@@ -625,7 +684,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
self._set_deadlines(course.id, upgrade_deadline=deadline, verification_deadline=deadline)
|
||||
|
||||
# Expect that the expiration date is set
|
||||
response = self._get_page("verify_student_start_flow", course.id)
|
||||
response = self._get_page(payment_flow, course.id)
|
||||
data = self._get_page_data(response)
|
||||
self.assertEqual(data['verification_deadline'], "Jan 02, 2999 at 00:00 UTC")
|
||||
|
||||
@@ -661,7 +720,9 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
|
||||
# Try to pay or upgrade.
|
||||
# We should get an error message since the deadline has passed.
|
||||
for page_name in ["verify_student_start_flow", "verify_student_upgrade_and_verify"]:
|
||||
for page_name in ["verify_student_start_flow",
|
||||
"verify_student_begin_flow",
|
||||
"verify_student_upgrade_and_verify"]:
|
||||
response = self._get_page(page_name, course.id)
|
||||
self.assertContains(response, "Upgrade Deadline Has Passed")
|
||||
|
||||
@@ -708,18 +769,20 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
self.assertContains(response, "Jan 02, 1999 at 00:00 UTC")
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {'EMBARGO': True})
|
||||
def test_embargo_restrict(self):
|
||||
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
||||
def test_embargo_restrict(self, payment_flow):
|
||||
course = self._create_course("verified")
|
||||
with restrict_course(course.id) as redirect_url:
|
||||
# Simulate that we're embargoed from accessing this
|
||||
# course based on our IP address.
|
||||
response = self._get_page('verify_student_start_flow', course.id, expected_status_code=302)
|
||||
response = self._get_page(payment_flow, course.id, expected_status_code=302)
|
||||
self.assertRedirects(response, redirect_url)
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {'EMBARGO': True})
|
||||
def test_embargo_allow(self):
|
||||
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
||||
def test_embargo_allow(self, payment_flow):
|
||||
course = self._create_course("verified")
|
||||
self._get_page('verify_student_start_flow', course.id)
|
||||
self._get_page(payment_flow, course.id)
|
||||
|
||||
def _create_course(self, *course_modes, **kwargs):
|
||||
"""Create a new course with the specified course modes. """
|
||||
@@ -918,7 +981,8 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
url = reverse('verify_student_upgrade_and_verify', kwargs={'course_id': unicode(course_id)})
|
||||
self.assertRedirects(response, url)
|
||||
|
||||
def test_course_upgrade_page_with_unicode_and_special_values_in_display_name(self):
|
||||
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
||||
def test_course_upgrade_page_with_unicode_and_special_values_in_display_name(self, payment_flow):
|
||||
"""Check the course information on the page. """
|
||||
mode_display_name = u"Introduction à l'astrophysique"
|
||||
course = CourseFactory.create(display_name=mode_display_name)
|
||||
@@ -932,13 +996,14 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
)
|
||||
|
||||
self._enroll(course.id)
|
||||
response_dict = self._get_page_data(self._get_page('verify_student_start_flow', course.id))
|
||||
response_dict = self._get_page_data(self._get_page(payment_flow, course.id))
|
||||
|
||||
self.assertEqual(response_dict['course_name'], mode_display_name)
|
||||
|
||||
@httpretty.activate
|
||||
@override_settings(ECOMMERCE_API_URL=TEST_API_URL, ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY)
|
||||
def test_processors_api(self):
|
||||
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
||||
def test_processors_api(self, payment_flow):
|
||||
"""
|
||||
Check that when working with a product being processed by the
|
||||
ecommerce api, we correctly call to that api for the list of
|
||||
@@ -957,7 +1022,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
content_type="application/json",
|
||||
)
|
||||
# make the server request
|
||||
response = self._get_page('verify_student_start_flow', course.id)
|
||||
response = self._get_page(payment_flow, course.id)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# ensure the mock api call was made. NOTE: the following line
|
||||
|
||||
@@ -23,6 +23,16 @@ urlpatterns = patterns(
|
||||
}
|
||||
),
|
||||
|
||||
# This is for A/B testing.
|
||||
url(
|
||||
r'^begin-flow/{course}/$'.format(course=settings.COURSE_ID_PATTERN),
|
||||
views.PayAndVerifyView.as_view(),
|
||||
name="verify_student_begin_flow",
|
||||
kwargs={
|
||||
'message': views.PayAndVerifyView.FIRST_TIME_VERIFY_MSG
|
||||
}
|
||||
),
|
||||
|
||||
# The user is enrolled in a non-paid mode and wants to upgrade.
|
||||
# This is the same as the "start verification" flow,
|
||||
# except with slight messaging changes.
|
||||
|
||||
@@ -423,7 +423,9 @@ class PayAndVerifyView(View):
|
||||
'verification_good_until': verification_good_until,
|
||||
'capture_sound': staticfiles_storage.url("audio/camera_capture.wav"),
|
||||
'nav_hidden': True,
|
||||
'is_ab_testing': 'begin-flow' in request.path,
|
||||
}
|
||||
|
||||
return render_to_response("verify_student/pay_and_verify.html", context)
|
||||
|
||||
def _redirect_if_necessary(
|
||||
|
||||
BIN
lms/static/images/icon-sm-professional.png
Normal file
BIN
lms/static/images/icon-sm-professional.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
@@ -674,6 +674,7 @@
|
||||
'lms/include/js/spec/verify_student/image_input_spec.js',
|
||||
'lms/include/js/spec/verify_student/review_photos_step_view_spec.js',
|
||||
'lms/include/js/spec/verify_student/make_payment_step_view_spec.js',
|
||||
'lms/include/js/spec/verify_student/make_payment_step_view_ab_testing_spec.js',
|
||||
'lms/include/js/spec/edxnotes/utils/logger_spec.js',
|
||||
'lms/include/js/spec/edxnotes/views/notes_factory_spec.js',
|
||||
'lms/include/js/spec/edxnotes/views/shim_spec.js',
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
define([
|
||||
'jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'common/js/spec_helpers/ajax_helpers',
|
||||
'common/js/spec_helpers/template_helpers',
|
||||
'js/verify_student/views/make_payment_step_view'
|
||||
],
|
||||
function( $, _, Backbone, AjaxHelpers, TemplateHelpers, MakePaymentStepView ) {
|
||||
'use strict';
|
||||
|
||||
var checkPaymentButtons,
|
||||
expectPaymentSubmitted,
|
||||
goToPayment,
|
||||
expectPaymentDisabledBecauseInactive,
|
||||
expectPaymentButtonEnabled,
|
||||
expectPriceSelected,
|
||||
createView,
|
||||
SERVER_ERROR_MSG = 'An error occurred!';
|
||||
|
||||
describe( 'edx.verify_student.MakePaymentStepView', function() {
|
||||
|
||||
var STEP_DATA = {
|
||||
minPrice: '12',
|
||||
currency: 'usd',
|
||||
processors: ['test-payment-processor'],
|
||||
courseKey: 'edx/test/test',
|
||||
courseModeSlug: 'verified',
|
||||
isABTesting: true
|
||||
};
|
||||
|
||||
createView = function( stepDataOverrides ) {
|
||||
var view = new MakePaymentStepView({
|
||||
el: $( '#current-step-container' ),
|
||||
stepData: _.extend( _.clone( STEP_DATA ), stepDataOverrides ),
|
||||
errorModel: new ( Backbone.Model.extend({}) )()
|
||||
}).render();
|
||||
|
||||
// Stub the payment form submission
|
||||
spyOn( view, 'submitForm' ).andCallFake( function() {} );
|
||||
return view;
|
||||
};
|
||||
|
||||
expectPriceSelected = function( price ) {
|
||||
var sel = $( 'input[name="contribution"]' );
|
||||
|
||||
// check that contribution value is same as price given
|
||||
expect( sel.length ).toEqual(1);
|
||||
expect( sel.val() ).toEqual(price);
|
||||
};
|
||||
|
||||
expectPaymentButtonEnabled = function( isEnabled ) {
|
||||
var el = $( '.payment-button'),
|
||||
appearsDisabled = el.hasClass( 'is-disabled' ),
|
||||
isDisabled = el.prop( 'disabled' );
|
||||
|
||||
expect( appearsDisabled ).not.toEqual( isEnabled );
|
||||
expect( isDisabled ).not.toEqual( isEnabled );
|
||||
};
|
||||
|
||||
expectPaymentDisabledBecauseInactive = function() {
|
||||
var payButton = $( '.payment-button' );
|
||||
|
||||
// Payment button should be hidden
|
||||
expect( payButton.length ).toEqual(0);
|
||||
};
|
||||
|
||||
|
||||
goToPayment = function( requests, kwargs ) {
|
||||
var params = {
|
||||
contribution: kwargs.amount || '',
|
||||
course_id: kwargs.courseId || '',
|
||||
processor: kwargs.processor || '',
|
||||
sku: kwargs.sku || ''
|
||||
};
|
||||
|
||||
// Click the "go to payment" button
|
||||
$( '.payment-button' ).click();
|
||||
|
||||
// Verify that the request was made to the server
|
||||
AjaxHelpers.expectPostRequest(
|
||||
requests, '/verify_student/create_order/', $.param( params )
|
||||
);
|
||||
|
||||
// Simulate the server response
|
||||
if ( kwargs.succeeds ) {
|
||||
// TODO put fixture responses in the right place
|
||||
AjaxHelpers.respondWithJson(
|
||||
requests, {payment_page_url: 'http://payment-page-url/', payment_form_data: {foo: 'bar'}}
|
||||
);
|
||||
} else {
|
||||
AjaxHelpers.respondWithTextError( requests, 400, SERVER_ERROR_MSG);
|
||||
}
|
||||
};
|
||||
|
||||
expectPaymentSubmitted = function( view, params ) {
|
||||
var form;
|
||||
|
||||
expect(view.submitForm).toHaveBeenCalled();
|
||||
form = view.submitForm.mostRecentCall.args[0];
|
||||
|
||||
expect(form.serialize()).toEqual($.param(params));
|
||||
expect(form.attr('method')).toEqual('POST');
|
||||
expect(form.attr('action')).toEqual('http://payment-page-url/');
|
||||
};
|
||||
|
||||
checkPaymentButtons = function( requests, buttons ) {
|
||||
var $el = $( '.payment-button' );
|
||||
expect($el.length).toEqual(_.size(buttons));
|
||||
_.each(buttons, function( expectedText, expectedId ) {
|
||||
var buttonEl = $( '#' + expectedId),
|
||||
request;
|
||||
|
||||
buttonEl.removeAttr('disabled');
|
||||
expect( buttonEl.length ).toEqual( 1 );
|
||||
expect( buttonEl[0] ).toHaveClass( 'payment-button' );
|
||||
expect( buttonEl[0] ).toHaveText( expectedText );
|
||||
expect( buttonEl[0] ).toHaveClass( 'action-primary-blue' );
|
||||
|
||||
buttonEl[0].click();
|
||||
expect( buttonEl[0] ).toHaveClass( 'is-selected' );
|
||||
expectPaymentButtonEnabled( false );
|
||||
request = AjaxHelpers.currentRequest(requests);
|
||||
expect(request.requestBody.split('&')).toContain('processor=' + expectedId);
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
window.analytics = jasmine.createSpyObj('analytics', ['track', 'page', 'trackLink']);
|
||||
|
||||
setFixtures( '<div id="current-step-container"></div>' );
|
||||
TemplateHelpers.installTemplate( 'templates/verify_student/make_payment_step_ab_testing' );
|
||||
});
|
||||
|
||||
it( 'A/B Testing: check Initialize method with AB testing enable ', function() {
|
||||
var view = createView();
|
||||
expect( view.templateName ).toEqual('make_payment_step_ab_testing');
|
||||
expect( view.btnClass ).toEqual('action-primary-blue');
|
||||
|
||||
});
|
||||
|
||||
it( 'shows users only minimum price', function() {
|
||||
var view = createView(),
|
||||
requests = AjaxHelpers.requests(this);
|
||||
|
||||
expectPriceSelected( STEP_DATA.minPrice );
|
||||
expectPaymentButtonEnabled( true );
|
||||
goToPayment( requests, {
|
||||
amount: STEP_DATA.minPrice,
|
||||
courseId: STEP_DATA.courseKey,
|
||||
processor: STEP_DATA.processors[0],
|
||||
succeeds: true
|
||||
});
|
||||
expectPaymentSubmitted( view, {foo: 'bar'} );
|
||||
});
|
||||
|
||||
it( 'A/B Testing: provides working payment buttons for a single processor', function() {
|
||||
createView({processors: ['cybersource']});
|
||||
checkPaymentButtons( AjaxHelpers.requests(this), {cybersource: 'Checkout'});
|
||||
});
|
||||
|
||||
it( 'A/B Testing: provides working payment buttons for multiple processors', function() {
|
||||
createView({processors: ['cybersource', 'paypal', 'other']});
|
||||
checkPaymentButtons( AjaxHelpers.requests(this), {
|
||||
cybersource: 'Checkout',
|
||||
paypal: 'Checkout with PayPal',
|
||||
other: 'Checkout with other'
|
||||
});
|
||||
});
|
||||
|
||||
it( 'A/B Testing: by default minimum price is selected if no suggested prices are given', function() {
|
||||
var view = createView(),
|
||||
requests = AjaxHelpers.requests( this );
|
||||
|
||||
expectPriceSelected( STEP_DATA.minPrice);
|
||||
expectPaymentButtonEnabled( true );
|
||||
|
||||
goToPayment( requests, {
|
||||
amount: STEP_DATA.minPrice,
|
||||
courseId: STEP_DATA.courseKey,
|
||||
processor: STEP_DATA.processors[0],
|
||||
succeeds: true
|
||||
});
|
||||
expectPaymentSubmitted( view, {foo: 'bar'} );
|
||||
});
|
||||
|
||||
it( 'A/B Testing: min price is always selected even if contribution amount is provided', function() {
|
||||
// Pre-select a price NOT in the suggestions
|
||||
createView({
|
||||
contributionAmount: '99.99'
|
||||
});
|
||||
|
||||
// Expect that the price is filled in
|
||||
expectPriceSelected( STEP_DATA.minPrice );
|
||||
});
|
||||
|
||||
it( 'A/B Testing: disables payment for inactive users', function() {
|
||||
createView({ isActive: false });
|
||||
expectPaymentDisabledBecauseInactive();
|
||||
});
|
||||
|
||||
it( 'A/B Testing: displays an error if the order could not be created', function() {
|
||||
var requests = AjaxHelpers.requests( this ),
|
||||
view = createView();
|
||||
|
||||
goToPayment( requests, {
|
||||
amount: STEP_DATA.minPrice,
|
||||
courseId: STEP_DATA.courseKey,
|
||||
processor: STEP_DATA.processors[0],
|
||||
succeeds: false
|
||||
});
|
||||
|
||||
// Expect that an error is displayed
|
||||
expect( view.errorModel.get('shown') ).toBe( true );
|
||||
expect( view.errorModel.get('errorTitle') ).toEqual( 'Could not submit order' );
|
||||
expect( view.errorModel.get('errorMsg') ).toEqual( SERVER_ERROR_MSG );
|
||||
|
||||
// Expect that the payment button is re-enabled
|
||||
expectPaymentButtonEnabled( true );
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -108,6 +108,7 @@ define([
|
||||
buttonEl.removeAttr('disabled');
|
||||
expect( buttonEl.length ).toEqual( 1 );
|
||||
expect( buttonEl[0] ).toHaveClass( 'payment-button' );
|
||||
expect( buttonEl[0] ).toHaveClass( 'action-primary' );
|
||||
expect( buttonEl[0] ).toHaveText( expectedText );
|
||||
|
||||
buttonEl[0].click();
|
||||
@@ -216,6 +217,12 @@ define([
|
||||
'Try the transaction again in a few minutes.'
|
||||
);
|
||||
});
|
||||
it( 'check Initialize method without AB testing ', function() {
|
||||
var view = createView();
|
||||
expect( view.templateName ).toEqual('make_payment_step');
|
||||
expect( view.btnClass ).toEqual('action-primary');
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -66,7 +66,8 @@ var edx = edx || {};
|
||||
verificationDeadline: el.data('verification-deadline'),
|
||||
courseModeSlug: el.data('course-mode-slug'),
|
||||
alreadyVerified: el.data('already-verified'),
|
||||
verificationGoodUntil: el.data('verification-good-until')
|
||||
verificationGoodUntil: el.data('verification-good-until'),
|
||||
isABTesting: el.data('is-ab-testing')
|
||||
},
|
||||
'payment-confirmation-step': {
|
||||
courseKey: el.data('course-key'),
|
||||
|
||||
@@ -11,6 +11,15 @@ var edx = edx || {};
|
||||
edx.verify_student.MakePaymentStepView = edx.verify_student.StepView.extend({
|
||||
|
||||
templateName: "make_payment_step",
|
||||
btnClass: 'action-primary',
|
||||
|
||||
initialize: function( obj ) {
|
||||
_.extend( this, obj );
|
||||
if (this.templateContext().isABTesting) {
|
||||
this.templateName = 'make_payment_step_ab_testing';
|
||||
this.btnClass = 'action-primary-blue';
|
||||
}
|
||||
},
|
||||
|
||||
defaultContext: function() {
|
||||
return {
|
||||
@@ -27,7 +36,8 @@ var edx = edx || {};
|
||||
platformName: '',
|
||||
alreadyVerified: false,
|
||||
courseModeSlug: 'audit',
|
||||
verificationGoodUntil: ''
|
||||
verificationGoodUntil: '',
|
||||
isABTesting: false
|
||||
};
|
||||
},
|
||||
|
||||
@@ -61,8 +71,8 @@ var edx = edx || {};
|
||||
_getPaymentButtonHtml: function(processorName) {
|
||||
var self = this;
|
||||
return _.template(
|
||||
'<button class="next action-primary payment-button" id="<%- name %>" ><%- text %></button> '
|
||||
)({name: processorName, text: self._getPaymentButtonText(processorName)});
|
||||
'<button class="next <%- btnClass %> payment-button" id="<%- name %>" ><%- text %></button> '
|
||||
)({name: processorName, text: self._getPaymentButtonText(processorName), btnClass: this.btnClass});
|
||||
},
|
||||
|
||||
postRender: function() {
|
||||
|
||||
@@ -175,6 +175,14 @@
|
||||
color: $white !important;
|
||||
}
|
||||
|
||||
// elements - controls
|
||||
.action-primary-blue {
|
||||
@extend %btn-primary-blue;
|
||||
// needed for override due to .register a:link styling
|
||||
border: 0 !important;
|
||||
color: $white !important;
|
||||
}
|
||||
|
||||
.action-confirm {
|
||||
@extend %btn-verify-primary;
|
||||
// needed for override due to .register a:link styling
|
||||
@@ -821,6 +829,109 @@
|
||||
|
||||
// indiv slides - review
|
||||
#wrapper-review {
|
||||
color: $black;
|
||||
|
||||
.page-title {
|
||||
@extend %t-strong;
|
||||
border-bottom: 2px solid $m-gray-d3;
|
||||
padding-bottom: ($baseline*0.75);
|
||||
margin-bottom: $baseline;
|
||||
text-transform: inherit;
|
||||
}
|
||||
|
||||
.review {
|
||||
|
||||
.certificate {
|
||||
@include font-size(18);
|
||||
background-repeat: no-repeat;
|
||||
padding-left: ($baseline*2.5);
|
||||
overflow: hidden;
|
||||
min-height: 32px;
|
||||
|
||||
p {
|
||||
@include line-height(22);
|
||||
@extend %t-strong;
|
||||
margin-top: 0;
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.purchase {
|
||||
@include float(right);
|
||||
@include margin-left($baseline*0.75);
|
||||
text-align: right;
|
||||
|
||||
.product-info {
|
||||
@include font-size(22);
|
||||
@extend %t-strong;
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
&.verified_icon {
|
||||
background-image: url('#{$static-path}/images/icon-sm-verified.png');
|
||||
}
|
||||
|
||||
&.no-id-professional_icon,
|
||||
&.professional_icon {
|
||||
background-image: url('#{$static-path}/images/icon-sm-professional.png');
|
||||
}
|
||||
}
|
||||
|
||||
.payment-buttons {
|
||||
overflow: auto;
|
||||
padding-bottom: ($baseline/4);
|
||||
margin: {
|
||||
top: ($baseline / 2);
|
||||
bottom: ($baseline * 0.75);
|
||||
};
|
||||
|
||||
.payment-button {
|
||||
padding: ($baseline*0.4) $baseline;
|
||||
min-width: 200px;
|
||||
}
|
||||
.action-primary-blue {
|
||||
&.is-selected {
|
||||
background: $blue !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.border-gray {
|
||||
border-bottom: 2px solid $gray;
|
||||
margin: ($baseline*1.12) 0;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: ($baseline*0.75) 0;
|
||||
|
||||
p {
|
||||
@include line-height(22);
|
||||
color: $black;
|
||||
}
|
||||
.photo-requirement {
|
||||
@include font-size(12);
|
||||
position: relative;
|
||||
padding-left: ($baseline*2);
|
||||
margin-top: ($baseline*0.75);
|
||||
background-repeat: no-repeat;
|
||||
background-position: left top;
|
||||
|
||||
.fa {
|
||||
position: absolute;
|
||||
left:0;
|
||||
color: $mediumGrey;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-weight: bold;
|
||||
color: $extraDarkGrey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.review-task {
|
||||
margin-bottom: ($baseline*1.5);
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
<div id="wrapper-review" tab-index="0" class="wrapper-view make-payment-step">
|
||||
<div class="review view">
|
||||
<% if (!isActive ) { %>
|
||||
<h2 class="page-title">
|
||||
<%- gettext("Account Not Activated")%>
|
||||
</h2>
|
||||
<% } else if ( !upgrade ) { %>
|
||||
<h2 class="page-title">
|
||||
<%= _.sprintf(
|
||||
gettext( "You are enrolling in %(courseName)s"),
|
||||
{ courseName: '<span class="course-title">' + courseName + '</span>' }
|
||||
) %>
|
||||
</h2>
|
||||
<% } else { %>
|
||||
<h2 class="page-title">
|
||||
<%= _.sprintf(
|
||||
gettext( "Upgrade to a Verified Certificate for %(courseName)s"),
|
||||
{ courseName: '<span class="course-title">' + courseName + '</span>' }
|
||||
) %>
|
||||
</h2>
|
||||
<% } %>
|
||||
|
||||
<% if ( !isActive ) { %>
|
||||
<p>
|
||||
<%- gettext("Before you upgrade to a certificate track, you must activate your account.") %>
|
||||
<%- gettext("Check your email for an activation message.") %>
|
||||
</p>
|
||||
<% } else { %>
|
||||
|
||||
<div class="certificate <%- courseModeSlug %>_icon">
|
||||
<div class="purchase">
|
||||
<p class="product-info"><span class="product-name"></span> <%- gettext( "Total" ) %>: <span class="price">$<%- minPrice %> USD</span></p>
|
||||
</div>
|
||||
<p>
|
||||
<% if ( courseModeSlug === 'no-id-professional' || courseModeSlug === 'professional') { %>
|
||||
<%= _.sprintf(
|
||||
gettext( "Professional Certificate for %(courseName)s"),{ courseName: courseName }
|
||||
)%>
|
||||
<% } else { %>
|
||||
<%= _.sprintf(
|
||||
gettext( "Verified Certificate for %(courseName)s"),{ courseName: courseName }
|
||||
)%>
|
||||
<% } %>
|
||||
</p>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
|
||||
<% if ( isActive ) { %>
|
||||
<div class="payment-buttons is-ready center">
|
||||
<input type="hidden" name="contribution" value="<%- minPrice %>" />
|
||||
<input type="hidden" name="sku" value="<%- sku %>" />
|
||||
<div class="pay-options">
|
||||
<%
|
||||
// payment buttons will go here
|
||||
%>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-gray"></div>
|
||||
<% } %>
|
||||
|
||||
</div>
|
||||
|
||||
<% if ( isActive ) { %>
|
||||
<div class="container">
|
||||
<% if ( _.some( requirements, function( isVisible ) { return isVisible; } ) ) { %>
|
||||
<p>
|
||||
<% if ( verificationDeadline ) { %>
|
||||
<%- _.sprintf(
|
||||
gettext( "To receive a certificate, you must also verify your identity before %(date)s." ),
|
||||
{ date: verificationDeadline }
|
||||
) %>
|
||||
<% } else { %>
|
||||
<%- gettext( "To receive a certificate, you must also verify your identity." ) %>
|
||||
<% } %>
|
||||
<%- gettext("To verify your identity, you need a webcam and a government-issued photo ID.") %>
|
||||
</p>
|
||||
<% if ( requirements['photo-id-required'] ) { %>
|
||||
<div class="photo-requirement user_icon">
|
||||
<i class="fa fa-user fa-2x" aria-hidden="true"></i>
|
||||
<h6>
|
||||
<%- gettext("Photo ID") %>
|
||||
</h6>
|
||||
<p>
|
||||
<%- gettext("Your ID must be a government-issued photo ID that clearly shows your face.") %>
|
||||
</p>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<% if ( requirements['webcam-required'] ) { %>
|
||||
<div class="photo-requirement cam_icon">
|
||||
<i class="fa fa-video-camera fa-2x" aria-hidden="true"></i>
|
||||
<h6>
|
||||
<%- gettext("Webcam") %>
|
||||
</h6>
|
||||
<p>
|
||||
<%- gettext("You will use your webcam to take a picture of your face and of your government-issued photo ID.") %>
|
||||
</p>
|
||||
</div>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</div>
|
||||
<% } %>
|
||||
<form id="payment-processor-form"></form>
|
||||
</div>
|
||||
@@ -24,9 +24,15 @@ from lms.djangoapps.verify_student.views import PayAndVerifyView
|
||||
<%
|
||||
template_names = (
|
||||
["webcam_photo", "image_input", "error"] +
|
||||
["intro_step", "make_payment_step", "payment_confirmation_step"] +
|
||||
["intro_step", "payment_confirmation_step"] +
|
||||
["face_photo_step", "id_photo_step", "review_photos_step", "enrollment_confirmation_step"]
|
||||
)
|
||||
|
||||
if not is_ab_testing:
|
||||
template_names.append("make_payment_step")
|
||||
else:
|
||||
template_names.append("make_payment_step_ab_testing")
|
||||
|
||||
%>
|
||||
% for template_name in template_names:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
@@ -76,6 +82,7 @@ from lms.djangoapps.verify_student.views import PayAndVerifyView
|
||||
data-already-verified='${already_verified}'
|
||||
data-verification-good-until='${verification_good_until}'
|
||||
data-capture-sound='${capture_sound}'
|
||||
data-is-ab-testing='${json.dumps(is_ab_testing)}'
|
||||
|
||||
## If we reached the verification flow from an in-course checkpoint,
|
||||
## then pass the checkpoint location in so that we can associate
|
||||
|
||||
Reference in New Issue
Block a user