Merge branch 'master' into areeb/discussions-edit-errant-string
This commit is contained in:
@@ -589,7 +589,7 @@ def _cert_info(user, enrollment, cert_status):
|
||||
linkedin_config = LinkedInAddToProfileConfiguration.current()
|
||||
if linkedin_config.is_enabled():
|
||||
status_dict['linked_in_url'] = linkedin_config.add_to_profile_url(
|
||||
course_overview.display_name, cert_status.get('mode'), cert_status['download_url'],
|
||||
course_overview, cert_status.get('mode'), cert_status['download_url'],
|
||||
)
|
||||
|
||||
if status in {'generating', 'downloadable', 'notpassing', 'restricted', 'auditing', 'unverified'}:
|
||||
|
||||
@@ -44,7 +44,7 @@ from eventtracking import tracker
|
||||
from model_utils.models import TimeStampedModel
|
||||
from opaque_keys.edx.django.models import CourseKeyField, LearningContextKeyField
|
||||
from pytz import UTC, timezone
|
||||
from user_util import user_util
|
||||
from openedx.core.lib import user_util
|
||||
|
||||
import openedx.core.djangoapps.django_comment_common.comment_client as cc
|
||||
from common.djangoapps.util.model_utils import emit_field_changed_events, get_changed_fields_dict
|
||||
@@ -1375,21 +1375,37 @@ class LinkedInAddToProfileConfiguration(ConfigurationModel):
|
||||
),
|
||||
)
|
||||
|
||||
@property
|
||||
def share_settings(self):
|
||||
"""
|
||||
Initialize share_settings once for reuse across methods
|
||||
"""
|
||||
if self._share_settings is None:
|
||||
self._share_settings = configuration_helpers.get_value(
|
||||
'SOCIAL_SHARING_SETTINGS',
|
||||
settings.SOCIAL_SHARING_SETTINGS
|
||||
)
|
||||
return self._share_settings
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._share_settings = None
|
||||
|
||||
def is_enabled(self, *key_fields): # pylint: disable=arguments-differ
|
||||
"""
|
||||
Checks both the model itself and share_settings to see if LinkedIn Add to Profile is enabled
|
||||
"""
|
||||
enabled = super().is_enabled(*key_fields)
|
||||
share_settings = configuration_helpers.get_value('SOCIAL_SHARING_SETTINGS', settings.SOCIAL_SHARING_SETTINGS)
|
||||
return share_settings.get('CERTIFICATE_LINKEDIN', enabled)
|
||||
return self.share_settings.get('CERTIFICATE_LINKEDIN', enabled)
|
||||
|
||||
def add_to_profile_url(self, course, cert_mode, cert_url, certificate=None):
|
||||
|
||||
def add_to_profile_url(self, course_name, cert_mode, cert_url, certificate=None):
|
||||
"""
|
||||
Construct the URL for the "add to profile" button. This will autofill the form based on
|
||||
the params provided.
|
||||
|
||||
Arguments:
|
||||
course_name (str): The display name of the course.
|
||||
course (CourseOverview): Course/CourseOverview Object.
|
||||
cert_mode (str): The course mode of the user's certificate (e.g. "verified", "honor", "professional")
|
||||
cert_url (str): The URL for the certificate.
|
||||
|
||||
@@ -1398,11 +1414,11 @@ class LinkedInAddToProfileConfiguration(ConfigurationModel):
|
||||
If provided, this function will also autofill the certId and issue date for the cert.
|
||||
"""
|
||||
params = {
|
||||
'name': self._cert_name(course_name, cert_mode),
|
||||
'name': self._cert_name(course.display_name, cert_mode),
|
||||
'certUrl': cert_url,
|
||||
}
|
||||
|
||||
params.update(self._organization_information())
|
||||
params.update(self._organization_information(course))
|
||||
|
||||
if certificate:
|
||||
params.update({
|
||||
@@ -1426,28 +1442,45 @@ class LinkedInAddToProfileConfiguration(ConfigurationModel):
|
||||
Returns:
|
||||
str: The formatted string to display for the name field on the LinkedIn Add to Profile dialog.
|
||||
"""
|
||||
default_cert_name = self.MODE_TO_CERT_NAME.get(cert_mode, _('{platform_name} Certificate for {course_name}'))
|
||||
default_cert_name = self.MODE_TO_CERT_NAME.get(
|
||||
cert_mode, _('{platform_name} Certificate for {course_name}')
|
||||
)
|
||||
# Look for an override of the certificate name in the SOCIAL_SHARING_SETTINGS setting
|
||||
share_settings = configuration_helpers.get_value('SOCIAL_SHARING_SETTINGS', settings.SOCIAL_SHARING_SETTINGS)
|
||||
cert_name = share_settings.get('CERTIFICATE_LINKEDIN_MODE_TO_CERT_NAME', {}).get(cert_mode, default_cert_name)
|
||||
cert_name = self.share_settings.get(
|
||||
'CERTIFICATE_LINKEDIN_MODE_TO_CERT_NAME', {}
|
||||
).get(cert_mode, default_cert_name)
|
||||
|
||||
return cert_name.format(
|
||||
platform_name=configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
|
||||
course_name=course_name
|
||||
)
|
||||
|
||||
def _organization_information(self):
|
||||
def _organization_information(self, course=None):
|
||||
"""
|
||||
Returns organization information for use in the URL parameters for add to profile.
|
||||
Returns organization information for use in the URL parameters for add to
|
||||
profile. By default when sharing to LinkedIn, Platform Name and/or Platform
|
||||
LINKEDIN_COMPANY_ID will be used. If Course specific Organization Name is
|
||||
prefered when sharing Certificate to linkedIn the flag for that
|
||||
CERTIFICATE_LINKEDIN_DEFAULTS_TO_COURSE_ORGANIZATION_NAME should be set
|
||||
to True alongside other LinkedIn settings
|
||||
|
||||
Returns:
|
||||
dict: Either the organization ID on LinkedIn or the organization's name
|
||||
dict: Either the organization ID on LinkedIn, the organization's name or
|
||||
organization name associated to a specific course
|
||||
Will be used to prefill the organization on the add to profile action.
|
||||
"""
|
||||
org_id = configuration_helpers.get_value('LINKEDIN_COMPANY_ID', self.company_identifier)
|
||||
prefer_course_organization_name = self.share_settings.get(
|
||||
'CERTIFICATE_LINKEDIN_DEFAULTS_TO_COURSE_ORGANIZATION_NAME', False
|
||||
)
|
||||
if (prefer_course_organization_name and course):
|
||||
return {"organizationName": course.display_organization}
|
||||
|
||||
org_id = configuration_helpers.get_value(
|
||||
"LINKEDIN_COMPANY_ID", self.company_identifier
|
||||
)
|
||||
# Prefer organization ID per documentation at https://addtoprofile.linkedin.com/
|
||||
if org_id:
|
||||
return {'organizationId': org_id}
|
||||
return {"organizationId": org_id}
|
||||
return {'organizationName': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)}
|
||||
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
from xmodule.modulestore.tests.django_utils import \
|
||||
SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from common.test.utils import assert_dict_contains_subset
|
||||
|
||||
|
||||
class TestUserProfileEvents(UserSettingsEventTestMixin, TestCase):
|
||||
@@ -271,7 +272,8 @@ class EnrollmentEventsTest(SharedModuleStoreTestCase, OpenEdxEventsTestMixin):
|
||||
enrollment = CourseEnrollment.enroll(self.user, self.course.id)
|
||||
|
||||
self.assertTrue(self.receiver_called)
|
||||
self.assertDictContainsSubset(
|
||||
assert_dict_contains_subset(
|
||||
self,
|
||||
{
|
||||
"signal": COURSE_ENROLLMENT_CREATED,
|
||||
"sender": None,
|
||||
@@ -294,7 +296,7 @@ class EnrollmentEventsTest(SharedModuleStoreTestCase, OpenEdxEventsTestMixin):
|
||||
creation_date=enrollment.created,
|
||||
),
|
||||
},
|
||||
event_receiver.call_args.kwargs
|
||||
event_receiver.call_args.kwargs,
|
||||
)
|
||||
|
||||
def test_enrollment_changed_event_emitted(self):
|
||||
@@ -314,7 +316,8 @@ class EnrollmentEventsTest(SharedModuleStoreTestCase, OpenEdxEventsTestMixin):
|
||||
enrollment.update_enrollment(mode="verified")
|
||||
|
||||
self.assertTrue(self.receiver_called)
|
||||
self.assertDictContainsSubset(
|
||||
assert_dict_contains_subset(
|
||||
self,
|
||||
{
|
||||
"signal": COURSE_ENROLLMENT_CHANGED,
|
||||
"sender": None,
|
||||
@@ -337,7 +340,7 @@ class EnrollmentEventsTest(SharedModuleStoreTestCase, OpenEdxEventsTestMixin):
|
||||
creation_date=enrollment.created,
|
||||
),
|
||||
},
|
||||
event_receiver.call_args.kwargs
|
||||
event_receiver.call_args.kwargs,
|
||||
)
|
||||
|
||||
def test_unenrollment_completed_event_emitted(self):
|
||||
@@ -357,7 +360,8 @@ class EnrollmentEventsTest(SharedModuleStoreTestCase, OpenEdxEventsTestMixin):
|
||||
CourseEnrollment.unenroll(self.user, self.course.id)
|
||||
|
||||
self.assertTrue(self.receiver_called)
|
||||
self.assertDictContainsSubset(
|
||||
assert_dict_contains_subset(
|
||||
self,
|
||||
{
|
||||
"signal": COURSE_UNENROLLMENT_COMPLETED,
|
||||
"sender": None,
|
||||
@@ -380,7 +384,7 @@ class EnrollmentEventsTest(SharedModuleStoreTestCase, OpenEdxEventsTestMixin):
|
||||
creation_date=enrollment.created,
|
||||
),
|
||||
},
|
||||
event_receiver.call_args.kwargs
|
||||
event_receiver.call_args.kwargs,
|
||||
)
|
||||
|
||||
|
||||
@@ -430,7 +434,8 @@ class TestCourseAccessRoleEvents(TestCase, OpenEdxEventsTestMixin):
|
||||
role.add_users(self.user)
|
||||
|
||||
self.assertTrue(self.receiver_called)
|
||||
self.assertDictContainsSubset(
|
||||
assert_dict_contains_subset(
|
||||
self,
|
||||
{
|
||||
"signal": COURSE_ACCESS_ROLE_ADDED,
|
||||
"sender": None,
|
||||
@@ -448,7 +453,7 @@ class TestCourseAccessRoleEvents(TestCase, OpenEdxEventsTestMixin):
|
||||
role=role._role_name, # pylint: disable=protected-access
|
||||
),
|
||||
},
|
||||
event_receiver.call_args.kwargs
|
||||
event_receiver.call_args.kwargs,
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
@@ -468,7 +473,8 @@ class TestCourseAccessRoleEvents(TestCase, OpenEdxEventsTestMixin):
|
||||
role.remove_users(self.user)
|
||||
|
||||
self.assertTrue(self.receiver_called)
|
||||
self.assertDictContainsSubset(
|
||||
assert_dict_contains_subset(
|
||||
self,
|
||||
{
|
||||
"signal": COURSE_ACCESS_ROLE_REMOVED,
|
||||
"sender": None,
|
||||
@@ -486,5 +492,5 @@ class TestCourseAccessRoleEvents(TestCase, OpenEdxEventsTestMixin):
|
||||
role=role._role_name, # pylint: disable=protected-access
|
||||
),
|
||||
},
|
||||
event_receiver.call_args.kwargs
|
||||
event_receiver.call_args.kwargs,
|
||||
)
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
"""Tests for LinkedIn Add to Profile configuration. """
|
||||
|
||||
|
||||
from types import SimpleNamespace
|
||||
from urllib.parse import quote
|
||||
import ddt
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
|
||||
@@ -17,6 +16,7 @@ class LinkedInAddToProfileUrlTests(TestCase):
|
||||
|
||||
COURSE_NAME = 'Test Course ☃'
|
||||
CERT_URL = 'http://s3.edx/cert'
|
||||
COURSE_ORGANIZATION = 'TEST+ORGANIZATION'
|
||||
SITE_CONFIGURATION = {
|
||||
'SOCIAL_SHARING_SETTINGS': {
|
||||
'CERTIFICATE_LINKEDIN_MODE_TO_CERT_NAME': {
|
||||
@@ -27,6 +27,17 @@ class LinkedInAddToProfileUrlTests(TestCase):
|
||||
}
|
||||
}
|
||||
}
|
||||
SITE_CONFIGURATION_COURSE_LEVEL_ORG = {
|
||||
'SOCIAL_SHARING_SETTINGS': {
|
||||
'CERTIFICATE_LINKEDIN_DEFAULTS_TO_COURSE_ORGANIZATION_NAME': True,
|
||||
'CERTIFICATE_LINKEDIN_MODE_TO_CERT_NAME': {
|
||||
'honor': '{platform_name} Honor Code Credential for {course_name}',
|
||||
'verified': '{platform_name} Verified Credential for {course_name}',
|
||||
'professional': '{platform_name} Professional Credential for {course_name}',
|
||||
'no-id-professional': '{platform_name} Professional Credential for {course_name}',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ddt.data(
|
||||
('honor', 'Honor+Code+Certificate+for+Test+Course+%E2%98%83'),
|
||||
@@ -49,7 +60,13 @@ class LinkedInAddToProfileUrlTests(TestCase):
|
||||
company_identifier=config.company_identifier,
|
||||
)
|
||||
|
||||
actual_url = config.add_to_profile_url(self.COURSE_NAME, cert_mode, self.CERT_URL)
|
||||
course_mock_object = SimpleNamespace(
|
||||
display_name=self.COURSE_NAME, display_organization=self.COURSE_ORGANIZATION
|
||||
)
|
||||
|
||||
actual_url = config.add_to_profile_url(
|
||||
course_mock_object, cert_mode, self.CERT_URL
|
||||
)
|
||||
|
||||
self.assertEqual(actual_url, expected_url)
|
||||
|
||||
@@ -74,8 +91,49 @@ class LinkedInAddToProfileUrlTests(TestCase):
|
||||
cert_url=quote(self.CERT_URL, safe=''),
|
||||
company_identifier=config.company_identifier,
|
||||
)
|
||||
|
||||
with with_site_configuration_context(configuration=self.SITE_CONFIGURATION):
|
||||
actual_url = config.add_to_profile_url(self.COURSE_NAME, cert_mode, self.CERT_URL)
|
||||
course_mock_object = SimpleNamespace(
|
||||
display_name=self.COURSE_NAME,
|
||||
display_organization=self.COURSE_ORGANIZATION,
|
||||
)
|
||||
actual_url = config.add_to_profile_url(
|
||||
course_mock_object, cert_mode, self.CERT_URL
|
||||
)
|
||||
self.assertEqual(actual_url, expected_url)
|
||||
|
||||
@ddt.data(
|
||||
('honor', 'Honor+Code+Credential+for+Test+Course+%E2%98%83'),
|
||||
('verified', 'Verified+Credential+for+Test+Course+%E2%98%83'),
|
||||
('professional', 'Professional+Credential+for+Test+Course+%E2%98%83'),
|
||||
('no-id-professional', 'Professional+Credential+for+Test+Course+%E2%98%83'),
|
||||
('default_mode', 'Certificate+for+Test+Course+%E2%98%83')
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_linked_in_url_with_course_org_name_override(
|
||||
self, cert_mode, expected_cert_name
|
||||
):
|
||||
config = LinkedInAddToProfileConfigurationFactory()
|
||||
|
||||
expected_url = (
|
||||
'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&'
|
||||
'name={platform}+{cert_name}&certUrl={cert_url}&'
|
||||
'organizationName={course_organization_name}'
|
||||
).format(
|
||||
platform=quote(settings.PLATFORM_NAME.encode('utf-8')),
|
||||
cert_name=expected_cert_name,
|
||||
cert_url=quote(self.CERT_URL, safe=''),
|
||||
course_organization_name=quote(self.COURSE_ORGANIZATION.encode('utf-8')),
|
||||
)
|
||||
|
||||
with with_site_configuration_context(
|
||||
configuration=self.SITE_CONFIGURATION_COURSE_LEVEL_ORG
|
||||
):
|
||||
course_mock_object = SimpleNamespace(
|
||||
display_name=self.COURSE_NAME,
|
||||
display_organization=self.COURSE_ORGANIZATION,
|
||||
)
|
||||
actual_url = config.add_to_profile_url(
|
||||
course_mock_object, cert_mode, self.CERT_URL
|
||||
)
|
||||
|
||||
self.assertEqual(actual_url, expected_url)
|
||||
|
||||
@@ -51,8 +51,6 @@ from openedx.features.course_experience.url_helpers import make_learning_mfe_cou
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.data import CertificatesDisplayBehaviors # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
BETA_TESTER_METHOD = 'common.djangoapps.student.helpers.access.is_beta_tester'
|
||||
@@ -426,6 +424,7 @@ class DashboardTest(ModuleStoreTestCase, TestVerificationBase):
|
||||
self.course.start = datetime.now(pytz.UTC) - timedelta(days=2)
|
||||
self.course.end = datetime.now(pytz.UTC) - timedelta(days=1)
|
||||
self.course.display_name = 'Omega'
|
||||
self.course.course_organization = 'Omega Org'
|
||||
self.course = self.update_course(self.course, self.user.id)
|
||||
|
||||
cert = GeneratedCertificateFactory.create(
|
||||
@@ -449,7 +448,7 @@ class DashboardTest(ModuleStoreTestCase, TestVerificationBase):
|
||||
).format(
|
||||
platform=quote(settings.PLATFORM_NAME.encode('utf-8')),
|
||||
cert_url=quote(cert.download_url, safe=''),
|
||||
company_identifier=linkedin_config.company_identifier
|
||||
company_identifier=linkedin_config.company_identifier,
|
||||
)
|
||||
|
||||
# Single assertion for the expected LinkedIn URL
|
||||
|
||||
@@ -30,6 +30,7 @@ from openedx.core.djangoapps.user_authn.views.login import login_user
|
||||
from openedx.features.enterprise_support.tests.factories import EnterpriseCustomerFactory
|
||||
|
||||
from .base import IntegrationTestMixin
|
||||
from common.test.utils import assert_dict_contains_subset
|
||||
|
||||
TESTSHIB_ENTITY_ID = "https://idp.testshib.org/idp/shibboleth"
|
||||
TESTSHIB_METADATA_URL = "https://mock.testshib.org/metadata/testshib-providers.xml"
|
||||
@@ -402,8 +403,10 @@ class TestShibIntegrationTest(SamlIntegrationTestUtilities, IntegrationTestMixin
|
||||
assert msg.startswith("SAML login %s")
|
||||
assert action_type == "request"
|
||||
assert idp_name == self.PROVIDER_IDP_SLUG
|
||||
self.assertDictContainsSubset(
|
||||
{"idp": idp_name, "auth_entry": "login", "next": expected_next_url}, request_data
|
||||
assert_dict_contains_subset(
|
||||
self,
|
||||
{"idp": idp_name, "auth_entry": "login", "next": expected_next_url},
|
||||
request_data,
|
||||
)
|
||||
assert next_url == expected_next_url
|
||||
assert "<samlp:AuthnRequest" in xml
|
||||
@@ -412,7 +415,7 @@ class TestShibIntegrationTest(SamlIntegrationTestUtilities, IntegrationTestMixin
|
||||
assert msg.startswith("SAML login %s")
|
||||
assert action_type == "response"
|
||||
assert idp_name == self.PROVIDER_IDP_SLUG
|
||||
self.assertDictContainsSubset({"RelayState": idp_name}, response_data)
|
||||
assert_dict_contains_subset(self, {"RelayState": idp_name}, response_data)
|
||||
assert "SAMLResponse" in response_data
|
||||
assert next_url == expected_next_url
|
||||
assert "<saml2p:Response" in xml
|
||||
|
||||
@@ -6,6 +6,7 @@ import ddt
|
||||
from common.djangoapps.third_party_auth.identityserver3 import IdentityServer3
|
||||
from common.djangoapps.third_party_auth.tests import testutil
|
||||
from common.djangoapps.third_party_auth.tests.utils import skip_unless_thirdpartyauth
|
||||
from common.test.utils import assert_dict_contains_subset
|
||||
|
||||
|
||||
@skip_unless_thirdpartyauth()
|
||||
@@ -97,7 +98,8 @@ class IdentityServer3Test(testutil.TestCase):
|
||||
Test user details fields are mapped to default keys
|
||||
"""
|
||||
provider_config = self.configure_identityServer3_provider(enabled=True)
|
||||
self.assertDictContainsSubset(
|
||||
assert_dict_contains_subset(
|
||||
self,
|
||||
{
|
||||
"username": "Edx",
|
||||
"email": "edxopenid@example.com",
|
||||
@@ -105,5 +107,5 @@ class IdentityServer3Test(testutil.TestCase):
|
||||
"last_name": "Openid",
|
||||
"fullname": "Edx Openid"
|
||||
},
|
||||
provider_config.backend_class().get_user_details(self.response)
|
||||
provider_config.backend_class().get_user_details(self.response),
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ from oauthlib.common import Request
|
||||
|
||||
from common.djangoapps.third_party_auth.lti import LTI_PARAMS_KEY, LTIAuthBackend
|
||||
from common.djangoapps.third_party_auth.tests.testutil import ThirdPartyAuthTestMixin
|
||||
from common.test.utils import assert_dict_contains_subset
|
||||
|
||||
|
||||
class UnitTestLTI(unittest.TestCase, ThirdPartyAuthTestMixin):
|
||||
@@ -55,10 +56,14 @@ class UnitTestLTI(unittest.TestCase, ThirdPartyAuthTestMixin):
|
||||
lti_max_timestamp_age=10
|
||||
)
|
||||
assert parameters
|
||||
self.assertDictContainsSubset({
|
||||
'custom_extra': 'parameter',
|
||||
'user_id': '292832126'
|
||||
}, parameters)
|
||||
assert_dict_contains_subset(
|
||||
self,
|
||||
{
|
||||
'custom_extra': 'parameter',
|
||||
'user_id': '292832126'
|
||||
},
|
||||
parameters,
|
||||
)
|
||||
|
||||
def test_validate_lti_valid_request_with_get_params(self):
|
||||
request = Request(
|
||||
@@ -72,10 +77,14 @@ class UnitTestLTI(unittest.TestCase, ThirdPartyAuthTestMixin):
|
||||
lti_max_timestamp_age=10
|
||||
)
|
||||
assert parameters
|
||||
self.assertDictContainsSubset({
|
||||
'custom_extra': 'parameter',
|
||||
'user_id': '292832126'
|
||||
}, parameters)
|
||||
assert_dict_contains_subset(
|
||||
self,
|
||||
{
|
||||
'custom_extra': 'parameter',
|
||||
'user_id': '292832126'
|
||||
},
|
||||
parameters,
|
||||
)
|
||||
|
||||
def test_validate_lti_old_timestamp(self):
|
||||
request = Request(
|
||||
|
||||
@@ -285,7 +285,6 @@ function getBaseConfig(config, useRequireJs) {
|
||||
'karma-chrome-launcher',
|
||||
'karma-firefox-launcher',
|
||||
'karma-spec-reporter',
|
||||
'karma-selenium-webdriver-launcher',
|
||||
'karma-webpack',
|
||||
'karma-sourcemap-loader',
|
||||
customPlugin
|
||||
@@ -332,36 +331,15 @@ function getBaseConfig(config, useRequireJs) {
|
||||
base: 'Firefox',
|
||||
prefs: {
|
||||
'app.update.auto': false,
|
||||
'app.update.enabled': false
|
||||
'app.update.enabled': false,
|
||||
'media.autoplay.default': 0, // allow autoplay
|
||||
'media.autoplay.blocking_policy': 0, // disable autoplay blocking
|
||||
'media.autoplay.allow-extension-background-pages': true,
|
||||
'media.autoplay.enabled.user-gestures-needed': false,
|
||||
}
|
||||
},
|
||||
ChromeDocker: {
|
||||
base: 'SeleniumWebdriver',
|
||||
browserName: 'chrome',
|
||||
getDriver: function() {
|
||||
return new webdriver.Builder()
|
||||
.forBrowser('chrome')
|
||||
.usingServer('http://edx.devstack.chrome:4444/wd/hub')
|
||||
.build();
|
||||
}
|
||||
},
|
||||
FirefoxDocker: {
|
||||
base: 'SeleniumWebdriver',
|
||||
browserName: 'firefox',
|
||||
getDriver: function() {
|
||||
var options = new firefox.Options(),
|
||||
profile = new firefox.Profile();
|
||||
profile.setPreference('focusmanager.testmode', true);
|
||||
options.setProfile(profile);
|
||||
return new webdriver.Builder()
|
||||
.forBrowser('firefox')
|
||||
.usingServer('http://edx.devstack.firefox:4444/wd/hub')
|
||||
.setFirefoxOptions(options)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: config.singleRun,
|
||||
|
||||
@@ -12,6 +12,15 @@ from django.dispatch import Signal
|
||||
from markupsafe import escape
|
||||
|
||||
|
||||
def assert_dict_contains_subset(test_case, subset, superset):
|
||||
"""
|
||||
Assert that `superset` includes all key/value pairs from `subset`.
|
||||
"""
|
||||
test_case.assertTrue(
|
||||
all(item in superset.items() for item in subset.items())
|
||||
)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def nostderr():
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user