diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f2454f5cc6..1a7454e1ec 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,8 @@ the Check/Final Check buttons with keys: custom_check and custom_final_check LMS: Add PaidCourseRegistration mode, where payment is required before course registration. +Studio: Switched to loading Javascript using require.js + LMS: Add split testing functionality for internal use. CMS: Add edit_course_tabs management command, providing a primitive @@ -36,7 +38,7 @@ new post dropdown as well as response and comment area labeling. LMS: enhanced shib support, including detection of linked shib account at login page and support for the ?next= GET parameter. -LMS: Experimental feature using the ICE change tracker JS pkg to allow peer +LMS: Experimental feature using the ICE change tracker JS pkg to allow peer assessors to edit the original submitter's work. LMS: Fixed a bug that caused links from forum user profile pages to @@ -341,4 +343,4 @@ Common: Allow setting of authentication session cookie name. LMS: Option to email students when enroll/un-enroll them. Blades: Added WAI-ARIA markup to the video player controls. These are now fully -accessible by screen readers. +accessible by screen readers. diff --git a/cms/djangoapps/contentstore/features/advanced-settings.py b/cms/djangoapps/contentstore/features/advanced-settings.py index 664d8d00ae..201d87f029 100644 --- a/cms/djangoapps/contentstore/features/advanced-settings.py +++ b/cms/djangoapps/contentstore/features/advanced-settings.py @@ -16,6 +16,11 @@ def i_select_advanced_settings(step): world.click_course_settings() link_css = 'li.nav-course-settings-advanced a' world.css_click(link_css) + world.wait_for_requirejs( + ["jquery", "js/models/course", "js/models/settings/advanced", + "js/views/settings/advanced", "codemirror"]) + # this shouldn't be necessary, but we experience sporadic failures otherwise + world.wait(1) @step('I am on the Advanced Course Settings page in Studio$') @@ -91,8 +96,10 @@ def assert_policy_entries(expected_keys, expected_values): index = get_index_of(key) assert_false(index == -1, "Could not find key: {key}".format(key=key)) found_value = world.css_find(VALUE_CSS)[index].value - assert_equal(value, found_value, - "Expected {} to have value {} but found {}".format(key, value, found_value)) + assert_equal( + value, found_value, + "Expected {} to have value {} but found {}".format(key, value, found_value) + ) def get_index_of(expected_key): @@ -116,4 +123,6 @@ def change_display_name_value(step, new_value): def change_value(step, key, new_value): type_in_codemirror(get_index_of(key), new_value) + world.wait(0.5) press_the_notification_button(step, "Save") + world.wait_for_ajax_complete() diff --git a/cms/djangoapps/contentstore/features/checklists.feature b/cms/djangoapps/contentstore/features/checklists.feature index 23995f5aaa..c2b411adf0 100644 --- a/cms/djangoapps/contentstore/features/checklists.feature +++ b/cms/djangoapps/contentstore/features/checklists.feature @@ -9,7 +9,8 @@ Feature: CMS.Course checklists Scenario: A course author can mark tasks as complete Given I have opened Checklists Then I can check and uncheck tasks in a checklist - And They are correctly selected after reloading the page + And I reload the page + Then the tasks are correctly selected # There are issues getting link to be active in browsers other than chrome @skip_firefox diff --git a/cms/djangoapps/contentstore/features/checklists.py b/cms/djangoapps/contentstore/features/checklists.py index 63762b70c1..8a8aad0a12 100644 --- a/cms/djangoapps/contentstore/features/checklists.py +++ b/cms/djangoapps/contentstore/features/checklists.py @@ -45,11 +45,11 @@ def i_can_check_and_uncheck_tasks(step): verifyChecklist2Status(2, 7, 29) -@step('They are correctly selected after reloading the page$') -def tasks_correctly_selected_after_reload(step): - reload_the_page(step) +@step('the tasks are correctly selected$') +def tasks_correctly_selected(step): verifyChecklist2Status(2, 7, 29) # verify that task 7 is still selected by toggling its checkbox state and making sure that it deselects + world.browser.execute_script("window.scrollBy(0,1000)") toggleTask(1, 6) verifyChecklist2Status(1, 7, 14) @@ -109,13 +109,15 @@ def toggleTask(checklist, task): # TODO: figure out a way to do this in phantom and firefox # For now we will mark the scenerios that use this method as skipped def clickActionLink(checklist, task, actionText): - # toggle checklist item to make sure that the link button is showing - toggleTask(checklist, task) - action_link = world.css_find('#course-checklist' + str(checklist) + ' a')[task] - # text will be empty initially, wait for it to populate def verify_action_link_text(driver): - return world.css_text('#course-checklist' + str(checklist) + ' a', index=task) == actionText + actualText = world.css_text('#course-checklist' + str(checklist) + ' a', index=task) + if actualText == actionText: + return True + else: + # toggle checklist item to make sure that the link button is showing + toggleTask(checklist, task) + return False world.wait_for(verify_action_link_text) world.css_click('#course-checklist' + str(checklist) + ' a', index=task) diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index 5dd72b3767..1cc35b761d 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -90,6 +90,7 @@ def press_the_notification_button(_step, name): world.browser.execute_script("$('{}').click()".format(btn_css)) else: world.css_click(btn_css) + world.wait_for_ajax_complete() @step('I change the "(.*)" field to "(.*)"$') @@ -244,7 +245,9 @@ def open_new_unit(step): step.given('I have opened a new course section in Studio') step.given('I have added a new subsection') step.given('I expand the first section') + old_url = world.browser.url world.css_click('a.new-unit-item') + world.wait_for(lambda x: world.browser.url != old_url) @step('the save notification button is disabled') @@ -298,6 +301,7 @@ def type_in_codemirror(index, text): g._element.send_keys(text) if world.is_firefox(): world.trigger_event('div.CodeMirror', index=index, event='blur') + world.wait_for_ajax_complete() def upload_file(filename): diff --git a/cms/djangoapps/contentstore/features/component.py b/cms/djangoapps/contentstore/features/component.py index 4fd7b01d9d..bec0c9431b 100644 --- a/cms/djangoapps/contentstore/features/component.py +++ b/cms/djangoapps/contentstore/features/component.py @@ -18,13 +18,22 @@ def add_unit(step): user = create_studio_user(is_staff=False) add_course_author(user, course) log_into_studio() - css_selectors = ['a.course-link', 'div.section-item a.expand-collapse-icon', 'a.new-unit-item'] + world.wait_for_requirejs([ + "jquery", "js/models/course", "coffee/src/models/module", + "coffee/src/views/unit", "jquery.ui", + ]) + world.wait_for_mathjax() + css_selectors = [ + 'a.course-link', 'div.section-item a.expand-collapse-icon', + 'a.new-unit-item', + ] for selector in css_selectors: world.css_click(selector) @step(u'I add this type of single step component:$') def add_a_single_step_component(step): + world.wait_for_xmodule() for step_hash in step.hashes: component = step_hash['Component'] assert_in(component, ['Discussion', 'Video']) @@ -67,6 +76,7 @@ def add_a_multi_step_component(step, is_advanced, category): def click_link(): link.click() + world.wait_for_xmodule() category = category.lower() for step_hash in step.hashes: css_selector = 'a[data-type="{}"]'.format(category) @@ -103,7 +113,7 @@ def see_a_multi_step_component(step, category): @step(u'I add a "([^"]*)" "([^"]*)" component$') -def add_component_catetory(step, component, category): +def add_component_category(step, component, category): assert category in ('single step', 'HTML', 'Problem', 'Advanced Problem') given_string = 'I add this type of {} component:'.format(category) step.given('{}\n{}\n{}'.format(given_string, '|Component|', '|{}|'.format(component))) @@ -111,6 +121,7 @@ def add_component_catetory(step, component, category): @step(u'I delete all components$') def delete_all_components(step): + world.wait_for_xmodule() delete_btn_css = 'a.delete-button' prompt_css = 'div#prompt-warning' btn_css = '{} a.button.action-primary'.format(prompt_css) @@ -118,7 +129,8 @@ def delete_all_components(step): count = len(world.css_find('ol.components li.component')) for _ in range(int(count)): world.css_click(delete_btn_css) - assert_true(world.is_css_present('{}.is-shown'.format(prompt_css)), + assert_true( + world.is_css_present('{}.is-shown'.format(prompt_css)), msg='Waiting for the confirmation prompt to be shown') # Pressing the button via css was not working reliably for the last component diff --git a/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py b/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py index aa7c0e4b6d..976cb3b21c 100644 --- a/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py +++ b/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py @@ -20,16 +20,21 @@ def create_component_instance(step, component_button_css, category, if has_multiple_templates: click_component_from_menu(category, boilerplate, expected_css) + if category in ('video',): + world.wait_for_xmodule() + assert_equal( 1, len(world.css_find(expected_css)), "Component instance with css {css} was not created successfully".format(css=expected_css)) - @world.absorb def click_new_component_button(step, component_button_css): step.given('I have clicked the new unit button') + world.wait_for_requirejs( + ["jquery", "js/models/course", "coffee/src/models/module", + "coffee/src/views/unit", "jquery.ui"]) world.css_click(component_button_css) @@ -50,6 +55,7 @@ def click_component_from_menu(category, boilerplate, expected_css): assert_equal(len(elements), 1) world.css_click(elem_css) + @world.absorb def edit_component_and_select_settings(): world.wait_for(lambda _driver: world.css_visible('a.edit-button')) @@ -107,6 +113,7 @@ def verify_all_setting_entries(expected_entries): @world.absorb def save_component_and_reopen(step): world.css_click("a.save-button") + world.wait_for_ajax_complete() # We have a known issue that modifications are still shown within the edit window after cancel (though) # they are not persisted. Refresh the browser to make sure the changes WERE persisted after Save. reload_the_page(step) @@ -136,6 +143,7 @@ def get_setting_entry(label): return None return world.retry_on_exception(get_setting) + @world.absorb def get_setting_entry_index(label): def get_index(): diff --git a/cms/djangoapps/contentstore/features/course-settings.feature b/cms/djangoapps/contentstore/features/course-settings.feature index 230b1a04ad..0aeb95dbd9 100644 --- a/cms/djangoapps/contentstore/features/course-settings.feature +++ b/cms/djangoapps/contentstore/features/course-settings.feature @@ -9,7 +9,8 @@ Feature: CMS.Course Settings When I select Schedule and Details And I set course dates And I press the "Save" notification button - Then I see the set dates on refresh + And I reload the page + Then I see the set dates # IE has trouble with saving information @skip_internetexplorer @@ -17,7 +18,8 @@ Feature: CMS.Course Settings Given I have set course dates And I clear all the dates except start And I press the "Save" notification button - Then I see cleared dates on refresh + And I reload the page + Then I see cleared dates # IE has trouble with saving information @skip_internetexplorer @@ -26,7 +28,8 @@ Feature: CMS.Course Settings And I press the "Save" notification button And I clear the course start date Then I receive a warning about course start date - And The previously set start date is shown on refresh + And I reload the page + And the previously set start date is shown # IE has trouble with saving information # Safari gets CSRF token errors @@ -37,7 +40,8 @@ Feature: CMS.Course Settings And I have entered a new course start date And I press the "Save" notification button Then The warning about course start date goes away - And My new course start date is shown on refresh + And I reload the page + Then my new course start date is shown # Safari does not save + refresh properly through sauce labs @skip_safari @@ -45,7 +49,8 @@ Feature: CMS.Course Settings Given I have set course dates And I press the "Save" notification button When I change fields - Then I do not see the new changes persisted on refresh + And I reload the page + Then I do not see the changes # Safari does not save + refresh properly through sauce labs @skip_safari diff --git a/cms/djangoapps/contentstore/features/course-settings.py b/cms/djangoapps/contentstore/features/course-settings.py index 5f026146b4..7ec6a1071a 100644 --- a/cms/djangoapps/contentstore/features/course-settings.py +++ b/cms/djangoapps/contentstore/features/course-settings.py @@ -31,6 +31,9 @@ def test_i_select_schedule_and_details(step): world.click_course_settings() link_css = 'li.nav-course-settings-schedule a' world.css_click(link_css) + world.wait_for_requirejs( + ["jquery", "js/models/course", + "js/models/settings/course_details", "js/views/settings/main"]) @step('I have set course dates$') @@ -51,12 +54,6 @@ def test_and_i_set_course_dates(step): set_date_or_time(ENROLLMENT_END_TIME_CSS, DUMMY_TIME) -@step('Then I see the set dates on refresh$') -def test_then_i_see_the_set_dates_on_refresh(step): - reload_the_page(step) - i_see_the_set_dates() - - @step('And I clear all the dates except start$') def test_and_i_clear_all_the_dates_except_start(step): set_date_or_time(COURSE_END_DATE_CSS, '') @@ -64,9 +61,8 @@ def test_and_i_clear_all_the_dates_except_start(step): set_date_or_time(ENROLLMENT_END_DATE_CSS, '') -@step('Then I see cleared dates on refresh$') -def test_then_i_see_cleared_dates_on_refresh(step): - reload_the_page(step) +@step('Then I see cleared dates$') +def test_then_i_see_cleared_dates(step): verify_date_or_time(COURSE_END_DATE_CSS, '') verify_date_or_time(ENROLLMENT_START_DATE_CSS, '') verify_date_or_time(ENROLLMENT_END_DATE_CSS, '') @@ -92,9 +88,8 @@ def test_i_receive_a_warning_about_course_start_date(step): assert_true('error' in world.css_find(COURSE_START_TIME_CSS).first._element.get_attribute('class')) -@step('The previously set start date is shown on refresh$') -def test_the_previously_set_start_date_is_shown_on_refresh(step): - reload_the_page(step) +@step('the previously set start date is shown$') +def test_the_previously_set_start_date_is_shown(step): verify_date_or_time(COURSE_START_DATE_CSS, '12/20/2013') verify_date_or_time(COURSE_START_TIME_CSS, DUMMY_TIME) @@ -118,9 +113,8 @@ def test_the_warning_about_course_start_date_goes_away(step): assert_false('error' in world.css_find(COURSE_START_TIME_CSS).first._element.get_attribute('class')) -@step('My new course start date is shown on refresh$') -def test_my_new_course_start_date_is_shown_on_refresh(step): - reload_the_page(step) +@step('my new course start date is shown$') +def new_course_start_date_is_shown(step): verify_date_or_time(COURSE_START_DATE_CSS, '12/22/2013') # Time should have stayed from before attempt to clear date. verify_date_or_time(COURSE_START_TIME_CSS, DUMMY_TIME) @@ -134,16 +128,6 @@ def test_i_change_fields(step): set_date_or_time(ENROLLMENT_END_DATE_CSS, '7/7/7777') -@step('I do not see the new changes persisted on refresh$') -def test_changes_not_shown_on_refresh(step): - step.then('Then I see the set dates on refresh') - - -@step('I do not see the changes') -def test_i_do_not_see_changes(_step): - i_see_the_set_dates() - - @step('I change the course overview') def test_change_course_overview(_step): type_in_codemirror(0, "
+
+
+
+
+
+
+ 
+ H1
@@ -49,7 +50,7 @@
${_("Multiple Choice")}
-
+
( ) red
@@ -60,7 +61,7 @@
${_("Checkboxes")}
-
+
[x] earth
@@ -71,7 +72,7 @@
${_("Text Input")}
-
+
= dog
@@ -80,7 +81,7 @@
${_("Numerical Input")}
-
+
= 3.14 +- 2%
@@ -89,7 +90,7 @@
${_("Dropdown")}
-
+
[[wrong, (right)]]
@@ -98,7 +99,7 @@
${_("Explanation")}
-
+
[explanation] A short explanation of the answer. [explanation]
diff --git a/cms/templates/widgets/segment-io.html b/cms/templates/widgets/segment-io.html
index f623c47542..a6a186b2a3 100644
--- a/cms/templates/widgets/segment-io.html
+++ b/cms/templates/widgets/segment-io.html
@@ -25,7 +25,7 @@
var course_location_analytics = "${context_course.location}";
%endif
var analytics = {
- track: function() { return; }
+ "track": function() {}
};
diff --git a/cms/templates/widgets/source-edit.html b/cms/templates/widgets/source-edit.html
index b7ee6c9db9..6797fd224f 100644
--- a/cms/templates/widgets/source-edit.html
+++ b/cms/templates/widgets/source-edit.html
@@ -26,9 +26,9 @@
-
-
+
diff --git a/cms/templates/widgets/tender.html b/cms/templates/widgets/tender.html
index 74318f7dac..eafcd75c18 100644
--- a/cms/templates/widgets/tender.html
+++ b/cms/templates/widgets/tender.html
@@ -1,13 +1,14 @@
% if user.is_authenticated():
-
-% endif
\ No newline at end of file
+% endif
diff --git a/cms/templates/widgets/upload_assets.html b/cms/templates/widgets/upload_assets.html
index 510313c905..9e9fd101fe 100644
--- a/cms/templates/widgets/upload_assets.html
+++ b/cms/templates/widgets/upload_assets.html
@@ -15,14 +15,13 @@
-
diff --git a/cms/templates/widgets/video/codemirror-edit.html b/cms/templates/widgets/video/codemirror-edit.html
index 10c28d829c..1db705e0ca 100644
--- a/cms/templates/widgets/video/codemirror-edit.html
+++ b/cms/templates/widgets/video/codemirror-edit.html
@@ -5,29 +5,29 @@
diff --git a/cms/urls.py b/cms/urls.py
index 1f7da09a8f..3f0a6f3076 100644
--- a/cms/urls.py
+++ b/cms/urls.py
@@ -102,6 +102,7 @@ urlpatterns = ('', # nopep8
# noop to squelch ajax errors
url(r'^event$', 'contentstore.views.event', name='event'),
+ url(r'^xmodule/', include('pipeline_js.urls')),
url(r'^heartbeat$', include('heartbeat.urls')),
)
@@ -132,7 +133,7 @@ js_info_dict = {
urlpatterns += (
# Serve catalog of localized strings to be rendered by Javascript
- url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
+ url(r'^i18n.js$', 'django.views.i18n.javascript_catalog', js_info_dict),
)
if settings.MITX_FEATURES.get('ENABLE_SERVICE_STATUS'):
diff --git a/common/djangoapps/mitxmako/middleware.py b/common/djangoapps/mitxmako/middleware.py
index daaddf6b87..c719c8c30d 100644
--- a/common/djangoapps/mitxmako/middleware.py
+++ b/common/djangoapps/mitxmako/middleware.py
@@ -12,10 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from dealer.git import git
from django.template import RequestContext
-
requestcontext = None
+
class MakoMiddleware(object):
def process_request(self, request):
@@ -23,3 +24,4 @@ class MakoMiddleware(object):
requestcontext = RequestContext(request)
requestcontext['is_secure'] = request.is_secure()
requestcontext['site'] = request.get_host()
+ requestcontext['REVISION'] = git.revision
diff --git a/common/djangoapps/pipeline_js/__init__.py b/common/djangoapps/pipeline_js/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/djangoapps/pipeline_js/templates/xmodule.js b/common/djangoapps/pipeline_js/templates/xmodule.js
new file mode 100644
index 0000000000..6a927c5718
--- /dev/null
+++ b/common/djangoapps/pipeline_js/templates/xmodule.js
@@ -0,0 +1,29 @@
+## This file is designed to load all the XModule Javascript files in one wad
+## using requirejs. It is passed through the Mako template system, which
+## populates the `urls` variable with a list of paths to XModule JS files.
+## These files assume that several libraries are available and bound to
+## variables in the global context, so we load those libraries with requirejs
+## and attach them to the global context manually.
+define(["jquery", "underscore", "youtube", "mathjax", "codemirror", "tinymce",
+ "jquery.tinymce", "jquery.qtip", "jquery.scrollTo", "jquery.flot",
+ "jquery.cookie",
+ "utility"],
+ function($, _, YT, MathJax, CodeMirror, tinymce) {
+ window.$ = $;
+ window._ = _;
+ window.YT = YT;
+ window.MathJax = MathJax;
+ window.CodeMirror = CodeMirror;
+ window.RequireJS = {
+ 'requirejs': requirejs,
+ 'require': require,
+ 'define': define
+ };
+
+ var urls = ${urls};
+ var head = $("head");
+ $.each(urls, function(i, url) {
+ head.append($("", {src: url}));
+ });
+ return window.XModule;
+});
diff --git a/common/djangoapps/pipeline_js/urls.py b/common/djangoapps/pipeline_js/urls.py
new file mode 100644
index 0000000000..1806680b56
--- /dev/null
+++ b/common/djangoapps/pipeline_js/urls.py
@@ -0,0 +1,9 @@
+"""
+URL patterns for Javascript files used to load all of the XModule JS in one wad.
+"""
+from django.conf.urls import url, patterns
+
+urlpatterns = patterns('pipeline_js.views', # nopep8
+ url(r'^files\.json$', 'xmodule_js_files', name='xmodule_js_files'),
+ url(r'^xmodule\.js$', 'requirejs_xmodule', name='requirejs_xmodule'),
+)
diff --git a/common/djangoapps/pipeline_js/views.py b/common/djangoapps/pipeline_js/views.py
new file mode 100644
index 0000000000..06b76900cf
--- /dev/null
+++ b/common/djangoapps/pipeline_js/views.py
@@ -0,0 +1,41 @@
+"""
+Views for returning XModule JS (used by requirejs)
+"""
+import json
+from django.conf import settings
+from django.http import HttpResponse
+from staticfiles.storage import staticfiles_storage
+from mitxmako.shortcuts import render_to_response
+
+
+def get_xmodule_urls():
+ """
+ Returns a list of the URLs to hit to grab all the XModule JS
+ """
+ if settings.DEBUG:
+ paths = [path.replace(".coffee", ".js") for path in
+ settings.PIPELINE_JS['module-js']['source_filenames']]
+ else:
+ paths = [settings.PIPELINE_JS['module-js']['output_filename']]
+ return [staticfiles_storage.url(path) for path in paths]
+
+
+def xmodule_js_files(request):
+ """
+ View function that returns XModule URLs as a JSON list; meant to be used
+ as an API
+ """
+ urls = get_xmodule_urls()
+ return HttpResponse(json.dumps(urls), content_type="application/json")
+
+
+def requirejs_xmodule(request):
+ """
+ View function that returns a requirejs-wrapped Javascript file that
+ loads all the XModule URLs; meant to be loaded via requireJS
+ """
+ return render_to_response(
+ "xmodule.js",
+ {"urls": get_xmodule_urls()},
+ content_type="text/javascript",
+ )
diff --git a/common/djangoapps/static_replace/__init__.py b/common/djangoapps/static_replace/__init__.py
index 712664bf39..bd0102272c 100644
--- a/common/djangoapps/static_replace/__init__.py
+++ b/common/djangoapps/static_replace/__init__.py
@@ -56,7 +56,7 @@ def replace_jump_to_id_urls(text, course_id, jump_to_id_base_url):
text: The content over which to perform the subtitutions
course_id: The course_id in which this rewrite happens
- jump_to_id_base_url:
+ jump_to_id_base_url:
A app-tier (e.g. LMS) absolute path to the base of the handler that will perform the
redirect. e.g. /courses////jump_to_id. NOTE the will be appended to
the end of this URL at re-write time
@@ -151,9 +151,11 @@ def replace_static_urls(text, data_directory, course_id=None, static_asset_path=
return "".join([quote, url, quote])
-
return re.sub(
- _url_replace_regex('/static/(?!{data_dir})'.format(data_dir=static_asset_path or data_directory)),
+ _url_replace_regex('(?:{static_url}|/static/)(?!{data_dir})'.format(
+ static_url=settings.STATIC_URL,
+ data_dir=static_asset_path or data_directory
+ )),
replace_static_url,
text
)
diff --git a/common/djangoapps/terrain/browser.py b/common/djangoapps/terrain/browser.py
index 2699dc8dfc..bb938347e1 100644
--- a/common/djangoapps/terrain/browser.py
+++ b/common/djangoapps/terrain/browser.py
@@ -106,6 +106,7 @@ def initial_setup(server):
# raise a WebDriverException
try:
world.browser = Browser(browser_driver)
+ world.browser.driver.set_script_timeout(10)
world.visit('/')
except WebDriverException:
@@ -132,6 +133,7 @@ def initial_setup(server):
**make_saucelabs_desired_capabilities()
)
world.absorb(30, 'IMPLICIT_WAIT')
+ world.browser.set_script_timeout(10)
elif world.LETTUCE_SELENIUM_CLIENT == 'grid':
world.browser = Browser(
@@ -140,6 +142,7 @@ def initial_setup(server):
browser=settings.SELENIUM_GRID.get('BROWSER'),
)
world.absorb(30, 'IMPLICIT_WAIT')
+ world.browser.driver.set_script_timeout(10)
else:
raise Exception("Unknown selenium client '{}'".format(world.LETTUCE_SELENIUM_CLIENT))
@@ -193,6 +196,7 @@ def screenshot_on_error(scenario):
except WebDriverException:
LOGGER.error('Could not capture a screenshot')
+
@after.all
def teardown_browser(total):
"""
diff --git a/common/djangoapps/terrain/steps.py b/common/djangoapps/terrain/steps.py
index 4191d1cb99..f9e9d93fc2 100644
--- a/common/djangoapps/terrain/steps.py
+++ b/common/djangoapps/terrain/steps.py
@@ -11,6 +11,7 @@
# Disable the "unused argument" warning because lettuce uses "step"
#pylint: disable=W0613
+import re
from lettuce import world, step
from .course_helpers import *
from .ui_helpers import *
@@ -21,14 +22,33 @@ from logging import getLogger
logger = getLogger(__name__)
-@step(r'I wait (?:for )?"(\d+)" seconds?$')
+@step(r'I wait (?:for )?"(\d+\.?\d*)" seconds?$')
def wait(step, seconds):
world.wait(seconds)
+REQUIREJS_WAIT = {
+ re.compile('settings-details'): [
+ "jquery", "js/models/course",
+ "js/models/settings/course_details", "js/views/settings/main"],
+ re.compile('settings-advanced'): [
+ "jquery", "js/models/course", "js/models/settings/advanced",
+ "js/views/settings/advanced", "codemirror"],
+ re.compile('edit\/.+vertical'): [
+ "jquery", "js/models/course", "coffee/src/models/module",
+ "coffee/src/views/unit", "jquery.ui"],
+}
+
@step('I reload the page$')
def reload_the_page(step):
+ world.wait_for_ajax_complete()
world.browser.reload()
+ requirements = None
+ for test, req in REQUIREJS_WAIT.items():
+ if test.search(world.browser.url):
+ requirements = req
+ break
+ world.wait_for_requirejs(requirements)
@step('I press the browser back button$')
@@ -177,6 +197,11 @@ def visit_url(step, url):
world.browser.visit(django_url(url))
+@step(u'wait for AJAX to (?:finish|complete)')
+def wait_ajax(_step):
+ wait_for_ajax_complete()
+
+
@step('I will confirm all alerts')
def i_confirm_all_alerts(step):
"""
diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py
index e400ca9b95..3fb5dfce67 100644
--- a/common/djangoapps/terrain/ui_helpers.py
+++ b/common/djangoapps/terrain/ui_helpers.py
@@ -3,10 +3,12 @@
from lettuce import world
import time
+import json
import platform
+from textwrap import dedent
from urllib import quote_plus
-from selenium.common.exceptions import WebDriverException, TimeoutException
-from selenium.common.exceptions import StaleElementReferenceException
+from selenium.common.exceptions import (
+ WebDriverException, TimeoutException, StaleElementReferenceException)
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
@@ -19,9 +21,155 @@ def wait(seconds):
time.sleep(float(seconds))
+# Selenium's `execute_async_script` function pauses Selenium's execution
+# until the browser calls a specific Javascript callback; in effect,
+# Selenium goes to sleep until the JS callback function wakes it back up again.
+# This callback is passed as the last argument to the script. Any arguments
+# passed to this callback get returned from the `execute_async_script`
+# function, which allows the JS to communicate information back to Python.
+# Ref: https://selenium.googlecode.com/svn/trunk/docs/api/dotnet/html/M_OpenQA_Selenium_IJavaScriptExecutor_ExecuteAsyncScript.htm
+
+
+@world.absorb
+def wait_for_js_variable_truthy(variable):
+ """
+ Using Selenium's `execute_async_script` function, poll the Javascript
+ enviornment until the given variable is defined and truthy. This process
+ guards against page reloads, and seamlessly retries on the next page.
+ """
+ js = """
+ var callback = arguments[arguments.length - 1];
+ var unloadHandler = function() {{
+ callback("unload");
+ }}
+ addEventListener("beforeunload", unloadHandler);
+ addEventListener("unload", unloadHandler);
+ var intervalID = setInterval(function() {{
+ try {{
+ if({variable}) {{
+ clearInterval(intervalID);
+ removeEventListener("beforeunload", unloadHandler);
+ removeEventListener("unload", unloadHandler);
+ callback(true);
+ }}
+ }} catch (e) {{}}
+ }}, 10);
+ """.format(variable=variable)
+ for _ in range(5): # 5 attempts max
+ result = world.browser.driver.execute_async_script(dedent(js))
+ if result == "unload":
+ # we ran this on the wrong page. Wait a bit, and try again, when the
+ # browser has loaded the next page.
+ world.wait(1)
+ continue
+ else:
+ return result
+
+
+@world.absorb
+def wait_for_xmodule():
+ "Wait until the XModule Javascript has loaded on the page."
+ world.wait_for_js_variable_truthy("XModule")
+
+
+@world.absorb
+def wait_for_mathjax():
+ "Wait until MathJax is loaded and set up on the page."
+ world.wait_for_js_variable_truthy("MathJax.isReady")
+
+
+class RequireJSError(Exception):
+ """
+ An error related to waiting for require.js. If require.js is unable to load
+ a dependency in the `wait_for_requirejs` function, Python will throw
+ this exception to make sure that the failure doesn't pass silently.
+ """
+ pass
+
+
+@world.absorb
+def wait_for_requirejs(dependencies=None):
+ """
+ If requirejs is loaded on the page, this function will pause
+ Selenium until require is finished loading the given dependencies.
+ If requirejs is not loaded on the page, this function will return
+ immediately.
+
+ :param dependencies: a list of strings that identify resources that
+ we should wait for requirejs to load. By default, requirejs will only
+ wait for jquery.
+ """
+ if not dependencies:
+ dependencies = ["jquery"]
+ # stick jquery at the front
+ if dependencies[0] != "jquery":
+ dependencies.insert(0, "jquery")
+
+ js = """
+ var callback = arguments[arguments.length - 1];
+ if(window.require) {{
+ requirejs.onError = callback;
+ var unloadHandler = function() {{
+ callback("unload");
+ }}
+ addEventListener("beforeunload", unloadHandler);
+ addEventListener("unload", unloadHandler);
+ require({deps}, function($) {{
+ setTimeout(function() {{
+ removeEventListener("beforeunload", unloadHandler);
+ removeEventListener("unload", unloadHandler);
+ callback(true);
+ }}, 50);
+ }});
+ }} else {{
+ callback(false);
+ }}
+ """.format(deps=json.dumps(dependencies))
+ for _ in range(5): # 5 attempts max
+ result = world.browser.driver.execute_async_script(dedent(js))
+ if result == "unload":
+ # we ran this on the wrong page. Wait a bit, and try again, when the
+ # browser has loaded the next page.
+ world.wait(1)
+ continue
+ elif result not in (None, True, False):
+ # we got a require.js error
+ msg = "Error loading dependencies: type={0} modules={1}".format(
+ result['requireType'], result['requireModules'])
+ err = RequireJSError(msg)
+ err.error = result
+ raise err
+ else:
+ return result
+
+
+@world.absorb
+def wait_for_ajax_complete():
+ """
+ Wait until all jQuery AJAX calls have completed. "Complete" means that
+ either the server has sent a response (regardless of whether the response
+ indicates success or failure), or that the AJAX call timed out waiting for
+ a response. For more information about the `jQuery.active` counter that
+ keeps track of this information, go here:
+ http://stackoverflow.com/questions/3148225/jquery-active-function#3148506
+ """
+ js = """
+ var callback = arguments[arguments.length - 1];
+ if(!window.jQuery) {callback(false);}
+ var intervalID = setInterval(function() {
+ if(jQuery.active == 0) {
+ clearInterval(intervalID);
+ callback(true);
+ }
+ }, 100);
+ """
+ world.browser.driver.execute_async_script(dedent(js))
+
+
@world.absorb
def visit(url):
world.browser.visit(django_url(url))
+ wait_for_requirejs()
@world.absorb
@@ -44,6 +192,7 @@ def is_css_not_present(css_selector, wait_time=5):
finally:
world.browser.driver.implicitly_wait(world.IMPLICIT_WAIT)
+
@world.absorb
def css_has_text(css_selector, text, index=0):
return world.css_text(css_selector, index=index) == text
@@ -58,6 +207,7 @@ def wait_for(func, timeout=5):
).until(func)
+@world.absorb
def wait_for_present(css_selector, timeout=30):
"""
Waiting for the element to be present in the DOM.
diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py
index 110d9788e6..f6caee41c7 100644
--- a/common/lib/capa/capa/inputtypes.py
+++ b/common/lib/capa/capa/inputtypes.py
@@ -256,6 +256,7 @@ class InputTypeBase(object):
'value': self.value,
'status': self.status,
'msg': self.msg,
+ 'STATIC_URL': self.system.STATIC_URL,
}
context.update((a, v) for (
a, v) in self.loaded_attributes.iteritems() if a in self.to_render)
@@ -507,7 +508,8 @@ class JSInput(InputTypeBase):
def _extra_context(self):
context = {
- 'applet_loader': '/static/js/capa/src/jsinput.js',
+ 'applet_loader': '{static_url}js/capa/src/jsinput.js'.format(
+ static_url=self.system.STATIC_URL),
'saved_state': self.value
}
@@ -1014,7 +1016,10 @@ class ChemicalEquationInput(InputTypeBase):
"""
TODO (vshnayder): Get rid of this once we have a standard way of requiring js to be loaded.
"""
- return {'previewer': '/static/js/capa/chemical_equation_preview.js', }
+ return {
+ 'previewer': '{static_url}js/capa/chemical_equation_preview.js'.format(
+ static_url=self.system.STATIC_URL),
+ }
def handle_ajax(self, dispatch, data):
'''
@@ -1098,8 +1103,9 @@ class FormulaEquationInput(InputTypeBase):
reported_status = self.status
return {
- 'previewer': '/static/js/capa/src/formula_equation_preview.js',
- 'reported_status': reported_status
+ 'previewer': '{static_url}js/capa/src/formula_equation_preview.js'.format(
+ static_url=self.system.STATIC_URL),
+ 'reported_status': reported_status,
}
def handle_ajax(self, dispatch, get):
@@ -1282,7 +1288,8 @@ class EditAMoleculeInput(InputTypeBase):
"""
"""
context = {
- 'applet_loader': '/static/js/capa/editamolecule.js',
+ 'applet_loader': '{static_url}js/capa/editamolecule.js'.format(
+ static_url=self.system.STATIC_URL),
}
return context
@@ -1318,7 +1325,8 @@ class DesignProtein2dInput(InputTypeBase):
"""
"""
context = {
- 'applet_loader': '/static/js/capa/design-protein-2d.js',
+ 'applet_loader': '{static_url}js/capa/design-protein-2d.js'.format(
+ static_url=self.system.STATIC_URL),
}
return context
@@ -1354,7 +1362,8 @@ class EditAGeneInput(InputTypeBase):
"""
"""
context = {
- 'applet_loader': '/static/js/capa/edit-a-gene.js',
+ 'applet_loader': '{static_url}js/capa/edit-a-gene.js'.format(
+ static_url=self.system.STATIC_URL),
}
return context
diff --git a/common/lib/capa/capa/templates/formulaequationinput.html b/common/lib/capa/capa/templates/formulaequationinput.html
index bff987e1ff..deb156b1a0 100644
--- a/common/lib/capa/capa/templates/formulaequationinput.html
+++ b/common/lib/capa/capa/templates/formulaequationinput.html
@@ -12,7 +12,7 @@
\[\]
-
+
diff --git a/common/lib/capa/capa/templates/imageinput.html b/common/lib/capa/capa/templates/imageinput.html
index 2c318370e7..394f180d3e 100644
--- a/common/lib/capa/capa/templates/imageinput.html
+++ b/common/lib/capa/capa/templates/imageinput.html
@@ -1,7 +1,7 @@
-
+
% if status == 'unsubmitted':
diff --git a/common/lib/capa/capa/tests/__init__.py b/common/lib/capa/capa/tests/__init__.py
index a1f75b0e92..1b93bc6123 100644
--- a/common/lib/capa/capa/tests/__init__.py
+++ b/common/lib/capa/capa/tests/__init__.py
@@ -1,7 +1,9 @@
import fs.osfs
-import os, os.path
+import os
+import os.path
from capa.capa_problem import LoncapaProblem
+from xmodule.x_module import ModuleSystem
from mock import Mock, MagicMock
import xml.sax.saxutils as saxutils
@@ -16,24 +18,30 @@ def tst_render_template(template, context):
"""
return '{0}'.format(saxutils.escape(repr(context)))
-def calledback_url(dispatch = 'score_update'):
+
+def calledback_url(dispatch='score_update'):
return dispatch
xqueue_interface = MagicMock()
xqueue_interface.send_to_queue.return_value = (0, 'Success!')
+
def test_system():
"""
Construct a mock ModuleSystem instance.
"""
the_system = Mock(
+ spec=ModuleSystem,
+ STATIC_URL='/dummy-static/',
+ DEBUG=True,
ajax_url='courses/course_id/modx/a_location',
track_function=Mock(),
get_module=Mock(),
render_template=tst_render_template,
replace_urls=Mock(),
user=Mock(),
+ seed=0,
filestore=fs.osfs.OSFS(os.path.join(TEST_DIR, "test_files")),
debug=True,
hostname="edx.org",
@@ -45,6 +53,7 @@ def test_system():
)
return the_system
+
def new_loncapa_problem(xml, system=None):
"""Construct a `LoncapaProblem` suitable for unit tests."""
return LoncapaProblem(xml, id='1', seed=723, system=system or test_system())
diff --git a/common/lib/capa/capa/tests/test_html_render.py b/common/lib/capa/capa/tests/test_html_render.py
index 8e343ee1cf..08f9b079a9 100644
--- a/common/lib/capa/capa/tests/test_html_render.py
+++ b/common/lib/capa/capa/tests/test_html_render.py
@@ -8,6 +8,7 @@ import mock
from .response_xml_factory import StringResponseXMLFactory, CustomResponseXMLFactory
from . import test_system, new_loncapa_problem
+
class CapaHtmlRenderTest(unittest.TestCase):
def setUp(self):
@@ -30,8 +31,10 @@ class CapaHtmlRenderTest(unittest.TestCase):
def test_include_html(self):
# Create a test file to include
- self._create_test_file('test_include.xml',
- 'Test include ')
+ self._create_test_file(
+ 'test_include.xml',
+ 'Test include '
+ )
# Generate some XML with an
xml_str = textwrap.dedent("""
@@ -148,16 +151,19 @@ class CapaHtmlRenderTest(unittest.TestCase):
# Expect that the template renderer was called with the correct
# arguments, once for the textline input and once for
# the solution
- expected_textline_context = {'status': 'unsubmitted',
- 'value': '',
- 'preprocessor': None,
- 'msg': '',
- 'inline': False,
- 'hidden': False,
- 'do_math': False,
- 'id': '1_2_1',
- 'trailing_text': '',
- 'size': None}
+ expected_textline_context = {
+ 'STATIC_URL': '/dummy-static/',
+ 'status': 'unsubmitted',
+ 'value': '',
+ 'preprocessor': None,
+ 'msg': '',
+ 'inline': False,
+ 'hidden': False,
+ 'do_math': False,
+ 'id': '1_2_1',
+ 'trailing_text': '',
+ 'size': None,
+ }
expected_solution_context = {'id': '1_solution_1'}
diff --git a/common/lib/capa/capa/tests/test_input_templates.py b/common/lib/capa/capa/tests/test_input_templates.py
index 8f6d926dba..887aaf05c4 100644
--- a/common/lib/capa/capa/tests/test_input_templates.py
+++ b/common/lib/capa/capa/tests/test_input_templates.py
@@ -37,15 +37,16 @@ class TemplateTestCase(unittest.TestCase):
self.template_path = os.path.join(capa_path,
'templates',
self.TEMPLATE_NAME)
- template_file = open(self.template_path)
- self.template = MakoTemplate(template_file.read())
- template_file.close()
+ with open(self.template_path) as f:
+ self.template = MakoTemplate(f.read())
def render_to_xml(self, context_dict):
"""
Render the template using the `context_dict` dict.
Returns an `etree` XML element.
"""
+ # add dummy STATIC_URL to template context
+ context_dict.setdefault("STATIC_URL", "/dummy-static/")
try:
xml_str = self.template.render_unicode(**context_dict)
except:
diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py
index 44c2f91199..d131aed020 100644
--- a/common/lib/capa/capa/tests/test_inputtypes.py
+++ b/common/lib/capa/capa/tests/test_inputtypes.py
@@ -51,12 +51,15 @@ class OptionInputTest(unittest.TestCase):
context = option_input._get_render_context() # pylint: disable=W0212
- expected = {'value': 'Down',
- 'options': [('Up', 'Up'), ('Down', 'Down')],
- 'status': 'answered',
- 'msg': '',
- 'inline': False,
- 'id': 'sky_input'}
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'value': 'Down',
+ 'options': [('Up', 'Up'), ('Down', 'Down')],
+ 'status': 'answered',
+ 'msg': '',
+ 'inline': False,
+ 'id': 'sky_input',
+ }
self.assertEqual(context, expected)
@@ -98,18 +101,20 @@ class ChoiceGroupTest(unittest.TestCase):
context = the_input._get_render_context() # pylint: disable=W0212
- expected = {'id': 'sky_input',
- 'value': 'foil3',
- 'status': 'answered',
- 'msg': '',
- 'input_type': expected_input_type,
- 'choices': [('foil1', 'This is foil One. '),
- ('foil2', 'This is foil Two. '),
- ('foil3', 'This is foil Three.'), ],
- 'show_correctness': 'always',
- 'submitted_message': 'Answer received.',
- 'name_array_suffix': expected_suffix, # what is this for??
- }
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'id': 'sky_input',
+ 'value': 'foil3',
+ 'status': 'answered',
+ 'msg': '',
+ 'input_type': expected_input_type,
+ 'choices': [('foil1', 'This is foil One. '),
+ ('foil2', 'This is foil Two. '),
+ ('foil3', 'This is foil Three.'), ],
+ 'show_correctness': 'always',
+ 'submitted_message': 'Answer received.',
+ 'name_array_suffix': expected_suffix, # what is this for??
+ }
self.assertEqual(context, expected)
@@ -148,14 +153,17 @@ class JavascriptInputTest(unittest.TestCase):
context = the_input._get_render_context() # pylint: disable=W0212
- expected = {'id': 'prob_1_2',
- 'status': 'unanswered',
- 'msg': '',
- 'value': '3',
- 'params': params,
- 'display_file': display_file,
- 'display_class': display_class,
- 'problem_state': problem_state, }
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'id': 'prob_1_2',
+ 'status': 'unanswered',
+ 'msg': '',
+ 'value': '3',
+ 'params': params,
+ 'display_file': display_file,
+ 'display_class': display_class,
+ 'problem_state': problem_state,
+ }
self.assertEqual(context, expected)
@@ -176,16 +184,19 @@ class TextLineTest(unittest.TestCase):
context = the_input._get_render_context() # pylint: disable=W0212
- expected = {'id': 'prob_1_2',
- 'value': 'BumbleBee',
- 'status': 'unanswered',
- 'size': size,
- 'msg': '',
- 'hidden': False,
- 'inline': False,
- 'do_math': False,
- 'trailing_text': '',
- 'preprocessor': None}
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'id': 'prob_1_2',
+ 'value': 'BumbleBee',
+ 'status': 'unanswered',
+ 'size': size,
+ 'msg': '',
+ 'hidden': False,
+ 'inline': False,
+ 'do_math': False,
+ 'trailing_text': '',
+ 'preprocessor': None,
+ }
self.assertEqual(context, expected)
def test_math_rendering(self):
@@ -204,17 +215,22 @@ class TextLineTest(unittest.TestCase):
context = the_input._get_render_context() # pylint: disable=W0212
- expected = {'id': 'prob_1_2',
- 'value': 'BumbleBee',
- 'status': 'unanswered',
- 'size': size,
- 'msg': '',
- 'hidden': False,
- 'inline': False,
- 'trailing_text': '',
- 'do_math': True,
- 'preprocessor': {'class_name': preprocessorClass,
- 'script_src': script}}
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'id': 'prob_1_2',
+ 'value': 'BumbleBee',
+ 'status': 'unanswered',
+ 'size': size,
+ 'msg': '',
+ 'hidden': False,
+ 'inline': False,
+ 'trailing_text': '',
+ 'do_math': True,
+ 'preprocessor': {
+ 'class_name': preprocessorClass,
+ 'script_src': script,
+ },
+ }
self.assertEqual(context, expected)
def test_trailing_text_rendering(self):
@@ -242,16 +258,19 @@ class TextLineTest(unittest.TestCase):
context = the_input._get_render_context() # pylint: disable=W0212
- expected = {'id': 'prob_1_2',
- 'value': 'BumbleBee',
- 'status': 'unanswered',
- 'size': size,
- 'msg': '',
- 'hidden': False,
- 'inline': False,
- 'do_math': False,
- 'trailing_text': expected_text,
- 'preprocessor': None}
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'id': 'prob_1_2',
+ 'value': 'BumbleBee',
+ 'status': 'unanswered',
+ 'size': size,
+ 'msg': '',
+ 'hidden': False,
+ 'inline': False,
+ 'do_math': False,
+ 'trailing_text': expected_text,
+ 'preprocessor': None,
+ }
self.assertEqual(context, expected)
@@ -280,13 +299,16 @@ class FileSubmissionTest(unittest.TestCase):
context = the_input._get_render_context() # pylint: disable=W0212
- expected = {'id': 'prob_1_2',
- 'status': 'queued',
- 'msg': input_class.submitted_msg,
- 'value': 'BumbleBee.py',
- 'queue_len': '3',
- 'allowed_files': '["runme.py", "nooooo.rb", "ohai.java"]',
- 'required_files': '["cookies.py"]'}
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'id': 'prob_1_2',
+ 'status': 'queued',
+ 'msg': input_class.submitted_msg,
+ 'value': 'BumbleBee.py',
+ 'queue_len': '3',
+ 'allowed_files': '["runme.py", "nooooo.rb", "ohai.java"]',
+ 'required_files': '["cookies.py"]',
+ }
self.assertEqual(context, expected)
@@ -325,17 +347,20 @@ class CodeInputTest(unittest.TestCase):
context = the_input._get_render_context() # pylint: disable=W0212
- expected = {'id': 'prob_1_2',
- 'value': 'print "good evening"',
- 'status': 'queued',
- 'msg': input_class.submitted_msg,
- 'mode': mode,
- 'linenumbers': linenumbers,
- 'rows': rows,
- 'cols': cols,
- 'hidden': '',
- 'tabsize': int(tabsize),
- 'queue_len': '3'}
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'id': 'prob_1_2',
+ 'value': 'print "good evening"',
+ 'status': 'queued',
+ 'msg': input_class.submitted_msg,
+ 'mode': mode,
+ 'linenumbers': linenumbers,
+ 'rows': rows,
+ 'cols': cols,
+ 'hidden': '',
+ 'tabsize': int(tabsize),
+ 'queue_len': '3',
+ }
self.assertEqual(context, expected)
@@ -375,19 +400,22 @@ class MatlabTest(unittest.TestCase):
def test_rendering(self):
context = self.the_input._get_render_context() # pylint: disable=W0212
- expected = {'id': 'prob_1_2',
- 'value': 'print "good evening"',
- 'status': 'queued',
- 'msg': self.input_class.submitted_msg,
- 'mode': self.mode,
- 'rows': self.rows,
- 'cols': self.cols,
- 'queue_msg': '',
- 'linenumbers': 'true',
- 'hidden': '',
- 'tabsize': int(self.tabsize),
- 'button_enabled': True,
- 'queue_len': '3'}
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'id': 'prob_1_2',
+ 'value': 'print "good evening"',
+ 'status': 'queued',
+ 'msg': self.input_class.submitted_msg,
+ 'mode': self.mode,
+ 'rows': self.rows,
+ 'cols': self.cols,
+ 'queue_msg': '',
+ 'linenumbers': 'true',
+ 'hidden': '',
+ 'tabsize': int(self.tabsize),
+ 'button_enabled': True,
+ 'queue_len': '3',
+ }
self.assertEqual(context, expected)
@@ -401,19 +429,22 @@ class MatlabTest(unittest.TestCase):
the_input = self.input_class(test_system(), elt, state)
context = the_input._get_render_context() # pylint: disable=W0212
- expected = {'id': 'prob_1_2',
- 'value': 'print "good evening"',
- 'status': 'queued',
- 'msg': self.input_class.submitted_msg,
- 'mode': self.mode,
- 'rows': self.rows,
- 'cols': self.cols,
- 'queue_msg': 'message',
- 'linenumbers': 'true',
- 'hidden': '',
- 'tabsize': int(self.tabsize),
- 'button_enabled': True,
- 'queue_len': '3'}
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'id': 'prob_1_2',
+ 'value': 'print "good evening"',
+ 'status': 'queued',
+ 'msg': self.input_class.submitted_msg,
+ 'mode': self.mode,
+ 'rows': self.rows,
+ 'cols': self.cols,
+ 'queue_msg': 'message',
+ 'linenumbers': 'true',
+ 'hidden': '',
+ 'tabsize': int(self.tabsize),
+ 'button_enabled': True,
+ 'queue_len': '3',
+ }
self.assertEqual(context, expected)
@@ -427,19 +458,22 @@ class MatlabTest(unittest.TestCase):
the_input = self.input_class(test_system(), elt, state)
context = the_input._get_render_context() # pylint: disable=W0212
- expected = {'id': 'prob_1_2',
- 'value': 'print "good evening"',
- 'status': status,
- 'msg': '',
- 'mode': self.mode,
- 'rows': self.rows,
- 'cols': self.cols,
- 'queue_msg': '',
- 'linenumbers': 'true',
- 'hidden': '',
- 'tabsize': int(self.tabsize),
- 'button_enabled': False,
- 'queue_len': '0'}
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'id': 'prob_1_2',
+ 'value': 'print "good evening"',
+ 'status': status,
+ 'msg': '',
+ 'mode': self.mode,
+ 'rows': self.rows,
+ 'cols': self.cols,
+ 'queue_msg': '',
+ 'linenumbers': 'true',
+ 'hidden': '',
+ 'tabsize': int(self.tabsize),
+ 'button_enabled': False,
+ 'queue_len': '0',
+ }
self.assertEqual(context, expected)
@@ -452,19 +486,22 @@ class MatlabTest(unittest.TestCase):
the_input = self.input_class(test_system(), elt, state)
context = the_input._get_render_context() # pylint: disable=W0212
- expected = {'id': 'prob_1_2',
- 'value': 'print "good evening"',
- 'status': 'queued',
- 'msg': self.input_class.plot_submitted_msg,
- 'mode': self.mode,
- 'rows': self.rows,
- 'cols': self.cols,
- 'queue_msg': '',
- 'linenumbers': 'true',
- 'hidden': '',
- 'tabsize': int(self.tabsize),
- 'button_enabled': True,
- 'queue_len': '1'}
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'id': 'prob_1_2',
+ 'value': 'print "good evening"',
+ 'status': 'queued',
+ 'msg': self.input_class.plot_submitted_msg,
+ 'mode': self.mode,
+ 'rows': self.rows,
+ 'cols': self.cols,
+ 'queue_msg': '',
+ 'linenumbers': 'true',
+ 'hidden': '',
+ 'tabsize': int(self.tabsize),
+ 'button_enabled': True,
+ 'queue_len': '1',
+ }
self.assertEqual(context, expected)
@@ -558,16 +595,19 @@ class SchematicTest(unittest.TestCase):
context = the_input._get_render_context() # pylint: disable=W0212
- expected = {'id': 'prob_1_2',
- 'value': value,
- 'status': 'unsubmitted',
- 'msg': '',
- 'initial_value': initial_value,
- 'width': width,
- 'height': height,
- 'parts': parts,
- 'analyses': analyses,
- 'submit_analyses': submit_analyses}
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'id': 'prob_1_2',
+ 'value': value,
+ 'status': 'unsubmitted',
+ 'msg': '',
+ 'initial_value': initial_value,
+ 'width': width,
+ 'height': height,
+ 'parts': parts,
+ 'analyses': analyses,
+ 'submit_analyses': submit_analyses,
+ }
self.assertEqual(context, expected)
@@ -597,15 +637,18 @@ class ImageInputTest(unittest.TestCase):
context = the_input._get_render_context() # pylint: disable=W0212
- expected = {'id': 'prob_1_2',
- 'value': value,
- 'status': 'unsubmitted',
- 'width': width,
- 'height': height,
- 'src': src,
- 'gx': egx,
- 'gy': egy,
- 'msg': ''}
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'id': 'prob_1_2',
+ 'value': value,
+ 'status': 'unsubmitted',
+ 'width': width,
+ 'height': height,
+ 'src': src,
+ 'gx': egx,
+ 'gy': egy,
+ 'msg': '',
+ }
self.assertEqual(context, expected)
@@ -648,12 +691,15 @@ class CrystallographyTest(unittest.TestCase):
context = the_input._get_render_context() # pylint: disable=W0212
- expected = {'id': 'prob_1_2',
- 'value': value,
- 'status': 'unsubmitted',
- 'msg': '',
- 'width': width,
- 'height': height}
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'id': 'prob_1_2',
+ 'value': value,
+ 'status': 'unsubmitted',
+ 'msg': '',
+ 'width': width,
+ 'height': height,
+ }
self.assertEqual(context, expected)
@@ -686,14 +732,17 @@ class VseprTest(unittest.TestCase):
context = the_input._get_render_context() # pylint: disable=W0212
- expected = {'id': 'prob_1_2',
- 'value': value,
- 'status': 'unsubmitted',
- 'msg': '',
- 'width': width,
- 'height': height,
- 'molecules': molecules,
- 'geometries': geometries}
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'id': 'prob_1_2',
+ 'value': value,
+ 'status': 'unsubmitted',
+ 'msg': '',
+ 'width': width,
+ 'height': height,
+ 'molecules': molecules,
+ 'geometries': geometries,
+ }
self.assertEqual(context, expected)
@@ -715,13 +764,15 @@ class ChemicalEquationTest(unittest.TestCase):
''' Verify that the render context matches the expected render context'''
context = self.the_input._get_render_context() # pylint: disable=W0212
- expected = {'id': 'prob_1_2',
- 'value': 'H2OYeah',
- 'status': 'unanswered',
- 'msg': '',
- 'size': self.size,
- 'previewer': '/static/js/capa/chemical_equation_preview.js',
- }
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'id': 'prob_1_2',
+ 'value': 'H2OYeah',
+ 'status': 'unanswered',
+ 'msg': '',
+ 'size': self.size,
+ 'previewer': '/dummy-static/js/capa/chemical_equation_preview.js',
+ }
self.assertEqual(context, expected)
def test_chemcalc_ajax_sucess(self):
@@ -801,13 +852,14 @@ class FormulaEquationTest(unittest.TestCase):
context = self.the_input._get_render_context() # pylint: disable=W0212
expected = {
+ 'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'value': 'x^2+1/2',
'status': 'unanswered',
'reported_status': '',
'msg': '',
'size': self.size,
- 'previewer': '/static/js/capa/src/formula_equation_preview.js',
+ 'previewer': '/dummy-static/js/capa/src/formula_equation_preview.js',
'inline': False,
}
self.assertEqual(context, expected)
@@ -946,12 +998,14 @@ class DragAndDropTest(unittest.TestCase):
the_input = lookup_tag('drag_and_drop_input')(test_system(), element, state)
context = the_input._get_render_context() # pylint: disable=W0212
- expected = {'id': 'prob_1_2',
- 'value': value,
- 'status': 'unsubmitted',
- 'msg': '',
- 'drag_and_drop_json': json.dumps(user_input)
- }
+ expected = {
+ 'STATIC_URL': '/dummy-static/',
+ 'id': 'prob_1_2',
+ 'value': value,
+ 'status': 'unsubmitted',
+ 'msg': '',
+ 'drag_and_drop_json': json.dumps(user_input)
+ }
# as we are dumping 'draggables' dicts while dumping user_input, string
# comparison will fail, as order of keys is random.
@@ -997,6 +1051,7 @@ class AnnotationInputTest(unittest.TestCase):
context = the_input._get_render_context() # pylint: disable=W0212
expected = {
+ 'STATIC_URL': '/dummy-static/',
'id': 'annotation_input',
'value': value,
'status': 'answered',
@@ -1073,6 +1128,7 @@ class TestChoiceText(unittest.TestCase):
]
expected = {
+ 'STATIC_URL': '/dummy-static/',
'msg': '',
'input_type': expected_input_type,
'choices': choices,
diff --git a/common/lib/xmodule/xmodule/js/RequireJS-namespace-undefine.js b/common/lib/xmodule/xmodule/js/RequireJS-namespace-undefine.js
new file mode 120000
index 0000000000..fa5392caaf
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/RequireJS-namespace-undefine.js
@@ -0,0 +1 @@
+../../../../../common/static/js/RequireJS-namespace-undefine.js
\ No newline at end of file
diff --git a/common/lib/xmodule/xmodule/js/js_test.yml b/common/lib/xmodule/xmodule/js/js_test.yml
index 008ed04335..7fb5699ecf 100644
--- a/common/lib/xmodule/xmodule/js/js_test.yml
+++ b/common/lib/xmodule/xmodule/js/js_test.yml
@@ -36,7 +36,8 @@ lib_paths:
- common_static/coffee/src/ajax_prefix.js
- common_static/coffee/src/logger.js
- common_static/js/vendor/jasmine-jquery.js
- - common_static/js/vendor/RequireJS.js
+ - common_static/js/vendor/require.js
+ - RequireJS-namespace-undefine.js
- common_static/js/vendor/jquery.min.js
- common_static/js/vendor/jquery-ui.min.js
- common_static/js/vendor/jquery.ui.draggable.js
diff --git a/common/lib/xmodule/xmodule/js/spec/html/edit_spec.coffee b/common/lib/xmodule/xmodule/js/spec/html/edit_spec.coffee
index 3b89d3500d..2b53ee2327 100644
--- a/common/lib/xmodule/xmodule/js/spec/html/edit_spec.coffee
+++ b/common/lib/xmodule/xmodule/js/spec/html/edit_spec.coffee
@@ -1,4 +1,8 @@
describe 'HTMLEditingDescriptor', ->
+ beforeEach ->
+ window.baseUrl = "/static/deadbeef"
+ afterEach ->
+ delete window.baseUrl
describe 'Read data from server, create Editor, and get data back out', ->
it 'Does not munge <', ->
# This is a test for Lighthouse #22,
diff --git a/common/lib/xmodule/xmodule/js/src/html/edit.coffee b/common/lib/xmodule/xmodule/js/src/html/edit.coffee
index 49f8168545..5ba7de874a 100644
--- a/common/lib/xmodule/xmodule/js/src/html/edit.coffee
+++ b/common/lib/xmodule/xmodule/js/src/html/edit.coffee
@@ -18,19 +18,19 @@ class @HTMLEditingDescriptor
# This is a workaround for the fact that tinyMCE's baseURL property is not getting correctly set on AWS
# instances (like sandbox). It is not necessary to explicitly set baseURL when running locally.
- tinyMCE.baseURL = '/static/js/vendor/tiny_mce'
+ tinyMCE.baseURL = "#{baseUrl}/js/vendor/tiny_mce"
@tiny_mce_textarea = $(".tiny-mce", @element).tinymce({
- script_url : '/static/js/vendor/tiny_mce/tiny_mce.js',
+ script_url : "#{baseUrl}/js/vendor/tiny_mce/tiny_mce.js",
theme : "advanced",
skin: 'studio',
schema: "html5",
# Necessary to preserve relative URLs to our images.
convert_urls : false,
# TODO: we should share this CSS with studio (and LMS)
- content_css : "/static/css/tiny-mce.css",
+ content_css : "#{baseUrl}/css/tiny-mce.css",
# The default popup_css path uses an absolute path referencing page in which tinyMCE is being hosted.
# Supply the correct relative path instead.
- popup_css: '/static/js/vendor/tiny_mce/themes/advanced/skins/default/dialog.css',
+ popup_css: "#{baseUrl}/js/vendor/tiny_mce/themes/advanced/skins/default/dialog.css",
formats : {
# Disable h4, h5, and h6 styles as we don't have CSS for them.
h4: {},
@@ -67,7 +67,7 @@ class @HTMLEditingDescriptor
setupTinyMCE: (ed) =>
ed.addButton('wrapAsCode', {
title : 'Code',
- image : '/static/images/ico-tinymce-code.png',
+ image : "#{baseUrl}/images/ico-tinymce-code.png",
onclick : () ->
ed.formatter.toggle('code')
# Without this, the dirty flag does not get set unless the user also types in text.
diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py
index 99caec0840..e4ce338458 100644
--- a/common/lib/xmodule/xmodule/tests/__init__.py
+++ b/common/lib/xmodule/xmodule/tests/__init__.py
@@ -50,6 +50,7 @@ def get_test_system(course_id=''):
"""
return ModuleSystem(
+ static_url='/static',
ajax_url='courses/course_id/modx/a_location',
track_function=Mock(),
get_module=Mock(),
diff --git a/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py b/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py
index 948b4f6e18..ea0cea800b 100644
--- a/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py
+++ b/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py
@@ -73,6 +73,7 @@ class TestXBlockWrapper(object):
render_template=lambda *args, **kwargs: u'{!r}, {!r}'.format(args, kwargs),
anonymous_student_id='dummy_anonymous_student_id',
open_ended_grading_interface={},
+ static_url='/static',
ajax_url='dummy_ajax_url',
xmodule_field_data=lambda d: d._field_data,
get_module=Mock(),
diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py
index 6b3e3b67c2..34a4b53d2e 100644
--- a/common/lib/xmodule/xmodule/x_module.py
+++ b/common/lib/xmodule/xmodule/x_module.py
@@ -794,7 +794,7 @@ class ModuleSystem(ConfigurableFragmentWrapper, Runtime): # pylint: disable=abs
and user, or other environment-specific info.
"""
def __init__(
- self, ajax_url, track_function, get_module, render_template,
+ self, static_url, ajax_url, track_function, get_module, render_template,
replace_urls, xmodule_field_data, user=None, filestore=None,
debug=False, hostname="", xqueue=None, publish=None, node_path="",
anonymous_student_id='', course_id=None,
@@ -804,6 +804,8 @@ class ModuleSystem(ConfigurableFragmentWrapper, Runtime): # pylint: disable=abs
"""
Create a closure around the system environment.
+ static_url - the base URL to static assets
+
ajax_url - the url where ajax calls to the encapsulating module go.
track_function - function of (event_type, event), intended for logging
@@ -856,6 +858,7 @@ class ModuleSystem(ConfigurableFragmentWrapper, Runtime): # pylint: disable=abs
# with an explicit field_data during construct_xblock, so None's suffice.
super(ModuleSystem, self).__init__(usage_store=None, field_data=None, **kwargs)
+ self.STATIC_URL = static_url
self.ajax_url = ajax_url
self.xqueue = xqueue
self.track_function = track_function
diff --git a/common/static/coffee/spec/logger_spec.coffee b/common/static/coffee/spec/logger_spec.coffee
index e8f174665d..69631170c9 100644
--- a/common/static/coffee/spec/logger_spec.coffee
+++ b/common/static/coffee/spec/logger_spec.coffee
@@ -1,35 +1,35 @@
describe 'Logger', ->
- it 'expose window.log_event', ->
- expect(window.log_event).toBe Logger.log
+ it 'expose window.log_event', ->
+ expect(window.log_event).toBe Logger.log
- describe 'log', ->
- it 'send a request to log event', ->
- spyOn $, 'postWithPrefix'
- Logger.log 'example', 'data'
- expect($.postWithPrefix).toHaveBeenCalledWith '/event',
- event_type: 'example'
- event: '"data"'
- page: window.location.href
+ describe 'log', ->
+ it 'send a request to log event', ->
+ spyOn jQuery, 'postWithPrefix'
+ Logger.log 'example', 'data'
+ expect(jQuery.postWithPrefix).toHaveBeenCalledWith '/event',
+ event_type: 'example'
+ event: '"data"'
+ page: window.location.href
- # Broken with commit 9f75e64? Skipping for now.
- xdescribe 'bind', ->
- beforeEach ->
- Logger.bind()
- Courseware.prefix = '/6002x'
+ # Broken with commit 9f75e64? Skipping for now.
+ xdescribe 'bind', ->
+ beforeEach ->
+ Logger.bind()
+ Courseware.prefix = '/6002x'
- afterEach ->
- window.onunload = null
+ afterEach ->
+ window.onunload = null
- it 'bind the onunload event', ->
- expect(window.onunload).toEqual jasmine.any(Function)
+ it 'bind the onunload event', ->
+ expect(window.onunload).toEqual jasmine.any(Function)
- it 'send a request to log event', ->
- spyOn($, 'ajax')
- window.onunload()
- expect($.ajax).toHaveBeenCalledWith
- url: "#{Courseware.prefix}/event",
- data:
- event_type: 'page_close'
- event: ''
- page: window.location.href
- async: false
+ it 'send a request to log event', ->
+ spyOn($, 'ajax')
+ window.onunload()
+ expect($.ajax).toHaveBeenCalledWith
+ url: "#{Courseware.prefix}/event",
+ data:
+ event_type: 'page_close'
+ event: ''
+ page: window.location.href
+ async: false
diff --git a/common/static/js/RequireJS-namespace-undefine.js b/common/static/js/RequireJS-namespace-undefine.js
new file mode 100644
index 0000000000..f72ebb6bbf
--- /dev/null
+++ b/common/static/js/RequireJS-namespace-undefine.js
@@ -0,0 +1,8 @@
+window.RequireJS = window.RequireJS || {};
+RequireJS.requirejs = RequireJS.requirejs || window.requirejs;
+RequireJS.require = RequireJS.require || window.require;
+RequireJS.define = RequireJS.define || window.define;
+
+window.require = undefined;
+window.define = undefined;
+window.requirejs = undefined;
diff --git a/common/static/js/RequireJS-namespace.js b/common/static/js/RequireJS-namespace.js
new file mode 100644
index 0000000000..b5367c7972
--- /dev/null
+++ b/common/static/js/RequireJS-namespace.js
@@ -0,0 +1,4 @@
+window.RequireJS = window.RequireJS || {};
+RequireJS.requirejs = RequireJS.requirejs || window.requirejs;
+RequireJS.require = RequireJS.require || window.require;
+RequireJS.define = RequireJS.define || window.define;
diff --git a/common/static/js/load_youtube.js b/common/static/js/load_youtube.js
new file mode 100644
index 0000000000..98f296d06a
--- /dev/null
+++ b/common/static/js/load_youtube.js
@@ -0,0 +1,5 @@
+define(["jquery"], function($) {
+ var url = "//www.youtube.com/player_api";
+ $("head").append($("", {src: url}));
+ return window.YT;
+});
diff --git a/common/static/js/src/utility.js b/common/static/js/src/utility.js
index f1e2958d93..d3ed461b38 100644
--- a/common/static/js/src/utility.js
+++ b/common/static/js/src/utility.js
@@ -22,9 +22,9 @@ window.isExternal = function (url) {
// Utility method for replacing a portion of a string.
window.rewriteStaticLinks = function(content, from, to) {
if (from === null || to === null) {
- return content
+ return content;
}
var regex = new RegExp(from, 'g');
- return content.replace(regex, to)
+ return content.replace(regex, to);
};
diff --git a/common/static/js/test/add_ajax_prefix.js b/common/static/js/test/add_ajax_prefix.js
index 16f0f7c14e..798ab5d870 100644
--- a/common/static/js/test/add_ajax_prefix.js
+++ b/common/static/js/test/add_ajax_prefix.js
@@ -1,5 +1,6 @@
// Tests require that addAjaxPrefix is called
// before the tests are run.
-AjaxPrefix.addAjaxPrefix(jQuery, function() {
- return "";
+AjaxPrefix.addAjaxPrefix($, function () {
+ return "";
});
+
diff --git a/common/static/js/vendor/RequireJS.js b/common/static/js/vendor/RequireJS.js
deleted file mode 100644
index a0526930ef..0000000000
--- a/common/static/js/vendor/RequireJS.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * This file is a wrapper for the Require JS file and module loader. Please see
- * the discussion at:
- *
- * https://edx-wiki.atlassian.net/wiki/display/LMS/Integration+of+Require+JS+into+the+system
- */
-
-var RequireJS = function() {
-
-// Below is the unmodified minified version of Require JS. The latest can be
-// found at:
-//
-// http://requirejs.org/docs/download.html
-
-/*
- RequireJS 2.1.2 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
- Available via the MIT or new BSD license.
- see: http://github.com/jrburke/requirejs for details
-*/
-var requirejs,require,define;
-(function(Y){function H(b){return"[object Function]"===L.call(b)}function I(b){return"[object Array]"===L.call(b)}function x(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(H(n)){if(this.events.error)try{e=j.execCb(c,n,b,e)}catch(d){a=d}else e=j.execCb(c,n,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=[this.map.id],a.requireType="define",C(this.error=a)}else e=n;this.exports=e;if(this.map.isDefine&&
-!this.ignore&&(p[c]=e,l.onResourceLoad))l.onResourceLoad(j,this.map,this.depMaps);delete k[c];this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=h(a.prefix);this.depMaps.push(d);s(d,"defined",t(this,function(e){var n,d;d=this.map.name;var v=this.map.parentMap?this.map.parentMap.name:null,f=j.makeRequire(a.parentMap,{enableBuildCallback:!0,
-skipMap:!0});if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,v,!0)})||""),e=h(a.prefix+"!"+d,this.map.parentMap),s(e,"defined",t(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=i(k,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",t(this,function(a){this.emit("error",a)}));d.enable()}}else n=t(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),n.error=t(this,function(a){this.inited=!0;this.error=
-a;a.requireModules=[b];E(k,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&delete k[a.map.id]});C(a)}),n.fromText=t(this,function(e,c){var d=a.name,u=h(d),v=O;c&&(e=c);v&&(O=!1);q(u);r(m.config,b)&&(m.config[d]=m.config[b]);try{l.exec(e)}catch(k){throw Error("fromText eval for "+d+" failed: "+k);}v&&(O=!0);this.depMaps.push(u);j.completeLoad(d);f([d],n)}),e.load(a.name,f,n,m)}));j.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){this.enabling=this.enabled=!0;x(this.depMaps,t(this,function(a,
-b){var c,e;if("string"===typeof a){a=h(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=i(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;s(a,"defined",t(this,function(a){this.defineDep(b,a);this.check()}));this.errback&&s(a,"error",this.errback)}c=a.id;e=k[c];!r(N,c)&&(e&&!e.enabled)&&j.enable(a,this)}));E(this.pluginMaps,t(this,function(a){var b=i(k,a.id);b&&!b.enabled&&j.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=
-this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){x(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};j={config:m,contextName:b,registry:k,defined:p,urlFetched:S,defQueue:F,Module:W,makeModuleMap:h,nextTick:l.nextTick,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=m.pkgs,c=m.shim,e={paths:!0,config:!0,map:!0};E(a,function(a,b){e[b]?"map"===b?Q(m[b],a,!0,!0):Q(m[b],a,!0):m[b]=a});a.shim&&(E(a.shim,function(a,
-b){I(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=j.makeShimExports(a);c[b]=a}),m.shim=c);a.packages&&(x(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,location:a.location||a.name,main:(a.main||"main").replace(ga,"").replace(aa,"")}}),m.pkgs=b);E(k,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=h(b))});if(a.deps||a.callback)j.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(Y,arguments));
-return b||a.exports&&Z(a.exports)}},makeRequire:function(a,d){function f(e,c,u){var i,m;d.enableBuildCallback&&(c&&H(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(H(c))return C(J("requireargs","Invalid require call"),u);if(a&&r(N,e))return N[e](k[a.id]);if(l.get)return l.get(j,e,a);i=h(e,a,!1,!0);i=i.id;return!r(p,i)?C(J("notloaded",'Module name "'+i+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):p[i]}K();j.nextTick(function(){K();m=q(h(null,a));m.skipMap=d.skipMap;
-m.init(e,c,u,{enabled:!0});B()});return f}d=d||{};Q(f,{isBrowser:z,toUrl:function(b){var d=b.lastIndexOf("."),g=null;-1!==d&&(g=b.substring(d,b.length),b=b.substring(0,d));return j.nameToUrl(c(b,a&&a.id,!0),g)},defined:function(b){return r(p,h(b,a,!1,!0).id)},specified:function(b){b=h(b,a,!1,!0).id;return r(p,b)||r(k,b)}});a||(f.undef=function(b){w();var c=h(b,a,!0),d=i(k,b);delete p[b];delete S[c.url];delete X[b];d&&(d.events.defined&&(X[b]=d.events),delete k[b])});return f},enable:function(a){i(k,
-a.id)&&q(a).enable()},completeLoad:function(a){var b,c,d=i(m.shim,a)||{},h=d.exports;for(w();F.length;){c=F.shift();if(null===c[0]){c[0]=a;if(b)break;b=!0}else c[0]===a&&(b=!0);D(c)}c=i(k,a);if(!b&&!r(p,a)&&c&&!c.inited){if(m.enforceDefine&&(!h||!Z(h)))return y(a)?void 0:C(J("nodefine","No define call for "+a,null,[a]));D([a,d.deps||[],d.exportsFn])}B()},nameToUrl:function(a,b){var c,d,h,f,j,k;if(l.jsExtRegExp.test(a))f=a+(b||"");else{c=m.paths;d=m.pkgs;f=a.split("/");for(j=f.length;0f.attachEvent.toString().indexOf("[native code"))&&!V?(O=!0,f.attachEvent("onreadystatechange",
-b.onScriptLoad)):(f.addEventListener("load",b.onScriptLoad,!1),f.addEventListener("error",b.onScriptError,!1)),f.src=d,K=f,D?A.insertBefore(f,D):A.appendChild(f),K=null,f;$&&(importScripts(d),b.completeLoad(c))};z&&M(document.getElementsByTagName("script"),function(b){A||(A=b.parentNode);if(s=b.getAttribute("data-main"))return q.baseUrl||(G=s.split("/"),ba=G.pop(),ca=G.length?G.join("/")+"/":"./",q.baseUrl=ca,s=ba),s=s.replace(aa,""),q.deps=q.deps?q.deps.concat(s):[s],!0});define=function(b,c,d){var i,
-f;"string"!==typeof b&&(d=c,c=b,b=null);I(c)||(d=c,c=[]);!c.length&&H(d)&&d.length&&(d.toString().replace(ia,"").replace(ja,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c));if(O){if(!(i=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),i=P;i&&(b||(b=i.getAttribute("data-requiremodule")),f=B[i.getAttribute("data-requirecontext")])}(f?f.defQueue:R).push([b,c,d])};define.amd=
-{jQuery:!0};l.exec=function(b){return eval(b)};l(q)}})(this);
-
-// The object which will be globally available via RequireJS variable.
-return {
- 'requirejs': requirejs,
- 'require': require,
- 'define': define
-};
-}(); // End-of: var RequireJS = function()
diff --git a/common/static/js/vendor/Squire.js b/common/static/js/vendor/Squire.js
new file mode 100644
index 0000000000..7784a5aece
--- /dev/null
+++ b/common/static/js/vendor/Squire.js
@@ -0,0 +1,250 @@
+define(function() {
+
+ /**
+ * Utility Functions
+ */
+
+ var toString = Object.prototype.toString;
+
+ var isArray = function(arr) {
+ return toString.call(arr) === '[object Array]';
+ };
+
+ var indexOf = function(arr, search) {
+ for (var i = 0, length = arr.length; i < length; i++) {
+ if (arr[i] === search) {
+ return i;
+ }
+ }
+
+ return -1;
+ };
+
+ var each = function(obj, iterator, context) {
+ var breaker = {};
+
+ if (obj === null) {
+ return;
+ }
+
+ if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
+ obj.forEach(iterator, context);
+ } else if (obj.length === +obj.length) {
+ for (var i = 0, l = obj.length; i < l; i++) {
+ if (iterator.call(context, obj[i], i, obj) === breaker){
+ return;
+ }
+ }
+ } else {
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ if (iterator.call(context, obj[key], key, obj) === breaker) {
+ return;
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * Require.js Abstractions
+ */
+
+ var getContext = function(id) {
+ return requirejs.s.contexts[id];
+ };
+
+ var undef = function(context, module) {
+ if (context.undef) {
+ return context.undef(module);
+ }
+
+ return context.require.undef(module);
+ };
+
+ /**
+ * Create a context name incrementor.
+ */
+ var idCounter = 0;
+ var uniqueId = function(prefix) {
+ var id = idCounter++;
+ return 'context' + id;
+ };
+
+ var Squire = function() {
+ this.mocks = {};
+ this._store = [];
+ this.requiredCallbacks = [];
+ this.configure.apply(this, arguments);
+ };
+
+ /**
+ * Hook to call when the require function is called.
+ */
+ Squire.prototype.onRequired = function(cb) {
+ this.requiredCallbacks.push(cb);
+ };
+
+ /**
+ * Configuration of Squire.js, called from constructor or manually takes the
+ * name of a require.js context to configure it.
+ */
+ Squire.prototype.configure = function(context) {
+ var configuration = {};
+ var property;
+
+ this.id = uniqueId();
+
+ // Default the context
+ if (typeof context === 'undefined') {
+ context = '_'; // Default require.js context
+ }
+
+ context = getContext(context);
+
+ if ( ! context) {
+ throw new Error('This context has not been created!');
+ }
+
+ each(context.config, function(property, key) {
+ if (key !== 'deps') {
+ configuration[key] = property;
+ }
+ });
+
+ configuration.context = this.id;
+
+ this.load = requirejs.config(configuration);
+ };
+
+ Squire.prototype.mock = function(path, mock) {
+ var alias;
+ if (typeof path === 'object') {
+ each(path, function(alias, key) {
+ this.mock(key, alias);
+ }, this);
+ } else {
+ this.mocks[path] = mock;
+ }
+
+ return this;
+ };
+
+ Squire.prototype.store = function(path) {
+ if (path && typeof path === 'string') {
+ this._store.push(path);
+ } else if(path && isArray(path)) {
+ each(path, function(pathToStore) {
+ this.store(pathToStore);
+ }, this);
+ }
+ return this;
+ };
+
+ Squire.prototype.require = function(dependencies, callback, errback) {
+ var magicModuleName = 'mocks';
+ var self = this;
+ var path, magicModuleLocation;
+
+ magicModuleLocation = indexOf(dependencies, magicModuleName);
+
+ if (magicModuleLocation !== -1) {
+ dependencies.splice(magicModuleLocation, 1);
+ }
+
+ each(this.mocks, function(mock, path) {
+ define(path, mock);
+ });
+
+ this.load(dependencies, function() {
+ var store = {};
+ var args = Array.prototype.slice.call(arguments);
+ var dependency;
+
+ if (magicModuleLocation !== -1) {
+ each(self._store, function(dependency) {
+ store[dependency] = getContext(self.id).defined[dependency];
+ });
+
+ args.splice(magicModuleLocation, 0, {
+ mocks: self.mocks,
+ store: store
+ });
+ }
+
+ callback.apply(null, args);
+
+ each(self.requiredCallbacks, function(cb) {
+ cb.call(null, dependencies, args);
+ });
+ }, errback);
+ };
+
+ Squire.prototype.clean = function(mock) {
+ var path;
+
+ if (mock && typeof mock === 'string') {
+ undef(getContext(this.id), mock);
+ delete this.mocks[mock];
+ } else if(mock && isArray(mock)) {
+ each(mock, function(mockToClean) {
+ this.clean(mockToClean);
+ }, this);
+ } else {
+ each(this.mocks, function(mock, path){
+ this.clean(path);
+ }, this);
+ }
+
+ return this;
+ };
+
+ Squire.prototype.remove = function() {
+ var path, context = getContext(this.id);
+ if(!context) { return; }
+
+ each(context.defined, function(dependency, path) {
+ undef(context, path);
+ }, this);
+
+ delete requirejs.s.contexts[this.id];
+ };
+
+ Squire.prototype.run = function(deps, callback) {
+ var self = this;
+ var run = function(done) {
+ self.require(deps, function() {
+ callback.apply(null, arguments);
+ done();
+ });
+ };
+
+ run.toString = function() {
+ return callback.toString();
+ };
+
+ return run;
+ };
+
+ /**
+ * Utilities
+ */
+
+ Squire.Helpers = {};
+
+ Squire.Helpers.returns = function(what) {
+ return function() {
+ return what;
+ };
+ };
+
+ Squire.Helpers.constructs = function(what) {
+ return function() {
+ return function() {
+ return what;
+ };
+ };
+ };
+
+ return Squire;
+});
diff --git a/common/static/js/vendor/Squire.min.js b/common/static/js/vendor/Squire.min.js
new file mode 100644
index 0000000000..00c16c73d0
--- /dev/null
+++ b/common/static/js/vendor/Squire.min.js
@@ -0,0 +1 @@
+define(function(){var a=Object.prototype.toString,b=function(b){return"[object Array]"===a.call(b)},c=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},d=function(a,b,c){var d={};if(null!==a)if(Array.prototype.forEach&&a.forEach===Array.prototype.forEach)a.forEach(b,c);else if(a.length===+a.length){for(var e=0,f=a.length;f>e;e++)if(b.call(c,a[e],e,a)===d)return}else for(var g in a)if(a.hasOwnProperty(g)&&b.call(c,a[g],g,a)===d)return},e=function(a){return requirejs.s.contexts[a]},f=function(a,b){return a.undef?a.undef(b):a.require.undef(b)},g=0,h=function(){var b=g++;return"context"+b},i=function(){this.mocks={},this._store=[],this.requiredCallbacks=[],this.configure.apply(this,arguments)};return i.prototype.onRequired=function(a){this.requiredCallbacks.push(a)},i.prototype.configure=function(a){var b={};if(this.id=h(),"undefined"==typeof a&&(a="_"),a=e(a),!a)throw new Error("This context has not been created!");d(a.config,function(a,c){"deps"!==c&&(b[c]=a)}),b.context=this.id,this.load=requirejs.config(b)},i.prototype.mock=function(a,b){return"object"==typeof a?d(a,function(a,b){this.mock(b,a)},this):this.mocks[a]=b,this},i.prototype.store=function(a){return a&&"string"==typeof a?this._store.push(a):a&&b(a)&&d(a,function(a){this.store(a)},this),this},i.prototype.require=function(a,b,f){var j,g="mocks",h=this;j=c(a,g),-1!==j&&a.splice(j,1),d(this.mocks,function(a,b){define(b,a)}),this.load(a,function(){var c={},f=Array.prototype.slice.call(arguments);-1!==j&&(d(h._store,function(a){c[a]=e(h.id).defined[a]}),f.splice(j,0,{mocks:h.mocks,store:c})),b.apply(null,f),d(h.requiredCallbacks,function(b){b.call(null,a,f)})},f)},i.prototype.clean=function(a){return a&&"string"==typeof a?(f(e(this.id),a),delete this.mocks[a]):a&&b(a)?d(a,function(a){this.clean(a)},this):d(this.mocks,function(a,b){this.clean(b)},this),this},i.prototype.remove=function(){var b=e(this.id);b&&(d(e(this.id).defined,function(a,c){f(b,c)},this),delete requirejs.s.contexts[this.id])},i.prototype.run=function(a,b){var c=this,d=function(d){c.require(a,function(){b.apply(null,arguments),d()})};return d.toString=function(){return b.toString()},d},i.Helpers={},i.Helpers.returns=function(a){return function(){return a}},i.Helpers.constructs=function(a){return function(){return function(){return a}}},i});
diff --git a/common/static/js/vendor/analytics.js b/common/static/js/vendor/analytics.js
index a63ff55587..1eeee20be9 100644
--- a/common/static/js/vendor/analytics.js
+++ b/common/static/js/vendor/analytics.js
@@ -3535,75 +3535,75 @@ module.exports = Provider.extend({
});
});
require.register("analytics/src/providers/foxmetrics.js", function(exports, require, module){
-// http://foxmetrics.com/documentation/apijavascript
-
-var Provider = require('../provider')
- , load = require('load-script');
-
-
-module.exports = Provider.extend({
-
- name : 'FoxMetrics',
-
- key : 'appId',
-
- defaults : {
- appId : null
- },
-
- initialize : function (options, ready) {
- var _fxm = window._fxm || {};
- window._fxm = _fxm.events || [];
- load('//d35tca7vmefkrc.cloudfront.net/scripts/' + options.appId + '.js');
-
- // FoxMetrics makes a queue, so it's ready immediately.
- ready();
- },
-
- identify : function (userId, traits) {
- // A `userId` is required for profile updates.
- if (!userId) return;
-
- // FoxMetrics needs the first and last name seperately. Fallback to
- // splitting the `name` trait if we don't have what we need.
- var firstName = traits.firstName
- , lastName = traits.lastName;
-
- if (!firstName && traits.name) firstName = traits.name.split(' ')[0];
- if (!lastName && traits.name) lastName = traits.name.split(' ')[1];
-
- window._fxm.push([
- '_fxm.visitor.profile',
- userId, // user id
- firstName, // first name
- lastName, // last name
- traits.email, // email
- traits.address, // address
- undefined, // social
- undefined, // partners
- traits // attributes
- ]);
- },
-
- track : function (event, properties) {
- window._fxm.push([
- event, // event name
- properties.category, // category
- properties // properties
- ]);
- },
-
- pageview : function (url) {
- window._fxm.push([
- '_fxm.pages.view',
- undefined, // title
- undefined, // name
- undefined, // category
- url, // url
- undefined // referrer
- ]);
- }
-
+// http://foxmetrics.com/documentation/apijavascript
+
+var Provider = require('../provider')
+ , load = require('load-script');
+
+
+module.exports = Provider.extend({
+
+ name : 'FoxMetrics',
+
+ key : 'appId',
+
+ defaults : {
+ appId : null
+ },
+
+ initialize : function (options, ready) {
+ var _fxm = window._fxm || {};
+ window._fxm = _fxm.events || [];
+ load('//d35tca7vmefkrc.cloudfront.net/scripts/' + options.appId + '.js');
+
+ // FoxMetrics makes a queue, so it's ready immediately.
+ ready();
+ },
+
+ identify : function (userId, traits) {
+ // A `userId` is required for profile updates.
+ if (!userId) return;
+
+ // FoxMetrics needs the first and last name seperately. Fallback to
+ // splitting the `name` trait if we don't have what we need.
+ var firstName = traits.firstName
+ , lastName = traits.lastName;
+
+ if (!firstName && traits.name) firstName = traits.name.split(' ')[0];
+ if (!lastName && traits.name) lastName = traits.name.split(' ')[1];
+
+ window._fxm.push([
+ '_fxm.visitor.profile',
+ userId, // user id
+ firstName, // first name
+ lastName, // last name
+ traits.email, // email
+ traits.address, // address
+ undefined, // social
+ undefined, // partners
+ traits // attributes
+ ]);
+ },
+
+ track : function (event, properties) {
+ window._fxm.push([
+ event, // event name
+ properties.category, // category
+ properties // properties
+ ]);
+ },
+
+ pageview : function (url) {
+ window._fxm.push([
+ '_fxm.pages.view',
+ undefined, // title
+ undefined, // name
+ undefined, // category
+ url, // url
+ undefined // referrer
+ ]);
+ }
+
});
});
require.register("analytics/src/providers/gauges.js", function(exports, require, module){
@@ -4067,54 +4067,54 @@ module.exports = [
});
require.register("analytics/src/providers/improvely.js", function(exports, require, module){
-// http://www.improvely.com/docs/landing-page-code
-// http://www.improvely.com/docs/conversion-code
-// http://www.improvely.com/docs/labeling-visitors
-
-var Provider = require('../provider')
- , alias = require('alias')
- , load = require('load-script');
-
-
-module.exports = Provider.extend({
-
- name : 'Improvely',
-
- defaults : {
- // Improvely requires two options: `domain` and `projectId`.
- domain : null,
- projectId : null
- },
-
- initialize : function (options, ready) {
- window._improvely = window._improvely || [];
- window.improvely = window.improvely || {
- init : function (e, t) { window._improvely.push(["init", e, t]); },
- goal : function (e) { window._improvely.push(["goal", e]); },
- label : function (e) { window._improvely.push(["label", e]); }
- };
-
- load('//' + options.domain + '.iljmp.com/improvely.js');
- window.improvely.init(options.domain, options.projectId);
-
- // Improvely creates a queue, so it's ready immediately.
- ready();
- },
-
- identify : function (userId, traits) {
- if (userId) window.improvely.label(userId);
- },
-
- track : function (event, properties) {
- // Improvely calls `revenue` `amount`, and puts the `event` in properties as
- // the `type`.
- properties || (properties = {});
- properties.type = event;
- alias(properties, { 'revenue' : 'amount' });
- window.improvely.goal(properties);
- }
-
-});
+// http://www.improvely.com/docs/landing-page-code
+// http://www.improvely.com/docs/conversion-code
+// http://www.improvely.com/docs/labeling-visitors
+
+var Provider = require('../provider')
+ , alias = require('alias')
+ , load = require('load-script');
+
+
+module.exports = Provider.extend({
+
+ name : 'Improvely',
+
+ defaults : {
+ // Improvely requires two options: `domain` and `projectId`.
+ domain : null,
+ projectId : null
+ },
+
+ initialize : function (options, ready) {
+ window._improvely = window._improvely || [];
+ window.improvely = window.improvely || {
+ init : function (e, t) { window._improvely.push(["init", e, t]); },
+ goal : function (e) { window._improvely.push(["goal", e]); },
+ label : function (e) { window._improvely.push(["label", e]); }
+ };
+
+ load('//' + options.domain + '.iljmp.com/improvely.js');
+ window.improvely.init(options.domain, options.projectId);
+
+ // Improvely creates a queue, so it's ready immediately.
+ ready();
+ },
+
+ identify : function (userId, traits) {
+ if (userId) window.improvely.label(userId);
+ },
+
+ track : function (event, properties) {
+ // Improvely calls `revenue` `amount`, and puts the `event` in properties as
+ // the `type`.
+ properties || (properties = {});
+ properties.type = event;
+ alias(properties, { 'revenue' : 'amount' });
+ window.improvely.goal(properties);
+ }
+
+});
});
require.register("analytics/src/providers/intercom.js", function(exports, require, module){
@@ -5533,6 +5533,10 @@ if (typeof exports == "object") {
module.exports = require("analytics");
} else if (typeof define == "function" && define.amd) {
define(function(){ return require("analytics"); });
+ // MODIFICATION:
+ // We have code, not using require, that expects analytics to be defined.
+ // Until we are using RequireJS throughout the product, define on the window object as well.
+ this["analytics"] = require("analytics");
} else {
this["analytics"] = require("analytics");
}})();
\ No newline at end of file
diff --git a/common/static/js/vendor/domReady.js b/common/static/js/vendor/domReady.js
new file mode 100644
index 0000000000..2b54122098
--- /dev/null
+++ b/common/static/js/vendor/domReady.js
@@ -0,0 +1,129 @@
+/**
+ * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
+ * Available via the MIT or new BSD license.
+ * see: http://github.com/requirejs/domReady for details
+ */
+/*jslint */
+/*global require: false, define: false, requirejs: false,
+ window: false, clearInterval: false, document: false,
+ self: false, setInterval: false */
+
+
+define(function () {
+ 'use strict';
+
+ var isTop, testDiv, scrollIntervalId,
+ isBrowser = typeof window !== "undefined" && window.document,
+ isPageLoaded = !isBrowser,
+ doc = isBrowser ? document : null,
+ readyCalls = [];
+
+ function runCallbacks(callbacks) {
+ var i;
+ for (i = 0; i < callbacks.length; i += 1) {
+ callbacks[i](doc);
+ }
+ }
+
+ function callReady() {
+ var callbacks = readyCalls;
+
+ if (isPageLoaded) {
+ //Call the DOM ready callbacks
+ if (callbacks.length) {
+ readyCalls = [];
+ runCallbacks(callbacks);
+ }
+ }
+ }
+
+ /**
+ * Sets the page as loaded.
+ */
+ function pageLoaded() {
+ if (!isPageLoaded) {
+ isPageLoaded = true;
+ if (scrollIntervalId) {
+ clearInterval(scrollIntervalId);
+ }
+
+ callReady();
+ }
+ }
+
+ if (isBrowser) {
+ if (document.addEventListener) {
+ //Standards. Hooray! Assumption here that if standards based,
+ //it knows about DOMContentLoaded.
+ document.addEventListener("DOMContentLoaded", pageLoaded, false);
+ window.addEventListener("load", pageLoaded, false);
+ } else if (window.attachEvent) {
+ window.attachEvent("onload", pageLoaded);
+
+ testDiv = document.createElement('div');
+ try {
+ isTop = window.frameElement === null;
+ } catch (e) {}
+
+ //DOMContentLoaded approximation that uses a doScroll, as found by
+ //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/,
+ //but modified by other contributors, including jdalton
+ if (testDiv.doScroll && isTop && window.external) {
+ scrollIntervalId = setInterval(function () {
+ try {
+ testDiv.doScroll();
+ pageLoaded();
+ } catch (e) {}
+ }, 30);
+ }
+ }
+
+ //Check if document already complete, and if so, just trigger page load
+ //listeners. Latest webkit browsers also use "interactive", and
+ //will fire the onDOMContentLoaded before "interactive" but not after
+ //entering "interactive" or "complete". More details:
+ //http://dev.w3.org/html5/spec/the-end.html#the-end
+ //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded
+ //Hmm, this is more complicated on further use, see "firing too early"
+ //bug: https://github.com/requirejs/domReady/issues/1
+ //so removing the || document.readyState === "interactive" test.
+ //There is still a window.onload binding that should get fired if
+ //DOMContentLoaded is missed.
+ if (document.readyState === "complete") {
+ pageLoaded();
+ }
+ }
+
+ /** START OF PUBLIC API **/
+
+ /**
+ * Registers a callback for DOM ready. If DOM is already ready, the
+ * callback is called immediately.
+ * @param {Function} callback
+ */
+ function domReady(callback) {
+ if (isPageLoaded) {
+ callback(doc);
+ } else {
+ readyCalls.push(callback);
+ }
+ return domReady;
+ }
+
+ domReady.version = '2.0.1';
+
+ /**
+ * Loader Plugin API method
+ */
+ domReady.load = function (name, req, onLoad, config) {
+ if (config.isBuild) {
+ onLoad(null);
+ } else {
+ domReady(onLoad);
+ }
+ };
+
+ /** END OF PUBLIC API **/
+
+ return domReady;
+});
diff --git a/common/static/js/vendor/jQuery-File-Upload/js/jquery.fileupload.js b/common/static/js/vendor/jQuery-File-Upload/js/jquery.fileupload.js
index 8755c082c3..f69528ac3d 100644
--- a/common/static/js/vendor/jQuery-File-Upload/js/jquery.fileupload.js
+++ b/common/static/js/vendor/jQuery-File-Upload/js/jquery.fileupload.js
@@ -18,7 +18,7 @@
// Register as an anonymous AMD module:
define([
'jquery',
- 'jquery.ui.widget'
+ 'jquery.ui'
], factory);
} else {
// Browser globals:
diff --git a/common/static/js/vendor/jasmine.async.js b/common/static/js/vendor/jasmine.async.js
new file mode 100644
index 0000000000..fb54f88114
--- /dev/null
+++ b/common/static/js/vendor/jasmine.async.js
@@ -0,0 +1,51 @@
+// Jasmine.Async, v0.1.0
+// Copyright (c)2012 Muted Solutions, LLC. All Rights Reserved.
+// Distributed under MIT license
+// http://github.com/derickbailey/jasmine.async
+this.AsyncSpec = (function(global){
+
+ // Private Methods
+ // ---------------
+
+ function runAsync(block){
+ return function(){
+ var done = false;
+ var complete = function(){ done = true; };
+
+ runs(function(){
+ block(complete);
+ });
+
+ waitsFor(function(){
+ return done;
+ });
+ };
+ }
+
+ // Constructor Function
+ // --------------------
+
+ function AsyncSpec(spec){
+ this.spec = spec;
+ }
+
+ // Public API
+ // ----------
+
+ AsyncSpec.prototype.beforeEach = function(block){
+ this.spec.beforeEach(runAsync(block));
+ };
+
+ AsyncSpec.prototype.afterEach = function(block){
+ this.spec.afterEach(runAsync(block));
+ };
+
+ AsyncSpec.prototype.it = function(description, block){
+ // For some reason, `it` is not attached to the current
+ // test suite, so it has to be called from the global
+ // context.
+ global.it(description, runAsync(block));
+ };
+
+ return AsyncSpec;
+})(this);
\ No newline at end of file
diff --git a/common/static/js/vendor/require.js b/common/static/js/vendor/require.js
new file mode 100644
index 0000000000..7ff409d9c8
--- /dev/null
+++ b/common/static/js/vendor/require.js
@@ -0,0 +1,36 @@
+/*
+ RequireJS 2.1.8 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
+ Available via the MIT or new BSD license.
+ see: http://github.com/jrburke/requirejs for details
+*/
+var requirejs,require,define;
+(function(Z){function H(b){return"[object Function]"===L.call(b)}function I(b){return"[object Array]"===L.call(b)}function y(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(H(m)){if(this.events.error&&this.map.isDefine||j.onError!==aa)try{e=i.execCb(c,m,b,e)}catch(d){a=d}else e=i.execCb(c,m,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==
+this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",v(this.error=a)}else e=m;this.exports=e;if(this.map.isDefine&&!this.ignore&&(r[c]=e,j.onResourceLoad))j.onResourceLoad(i,this.map,this.depMaps);x(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=
+!0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=n(a.prefix);this.depMaps.push(d);t(d,"defined",u(this,function(e){var m,d;d=this.map.name;var g=this.map.parentMap?this.map.parentMap.name:null,h=i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,g,!0)})||""),e=n(a.prefix+"!"+d,this.map.parentMap),t(e,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),
+d=l(p,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else m=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),m.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];F(p,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&x(a.map.id)});v(a)}),m.fromText=u(this,function(e,c){var d=a.name,g=n(d),B=O;c&&(e=c);B&&(O=!1);q(g);s(k.config,b)&&(k.config[d]=k.config[b]);try{j.exec(e)}catch(ca){return v(A("fromtexteval",
+"fromText eval for "+b+" failed: "+ca,ca,[b]))}B&&(O=!0);this.depMaps.push(g);i.completeLoad(d);h([d],m)}),e.load(a.name,h,m,k)}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){T[this.map.id]=this;this.enabling=this.enabled=!0;y(this.depMaps,u(this,function(a,b){var c,e;if("string"===typeof a){a=n(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=l(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;t(a,"defined",u(this,function(a){this.defineDep(b,
+a);this.check()}));this.errback&&t(a,"error",u(this,this.errback))}c=a.id;e=p[c];!s(N,c)&&(e&&!e.enabled)&&i.enable(a,this)}));F(this.pluginMaps,u(this,function(a){var b=l(p,a.id);b&&!b.enabled&&i.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){y(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:k,contextName:b,registry:p,defined:r,urlFetched:S,defQueue:G,Module:X,makeModuleMap:n,
+nextTick:j.nextTick,onError:v,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=k.pkgs,c=k.shim,e={paths:!0,config:!0,map:!0};F(a,function(a,b){e[b]?"map"===b?(k.map||(k.map={}),Q(k[b],a,!0,!0)):Q(k[b],a,!0):k[b]=a});a.shim&&(F(a.shim,function(a,b){I(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);c[b]=a}),k.shim=c);a.packages&&(y(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,
+location:a.location||a.name,main:(a.main||"main").replace(ja,"").replace(ea,"")}}),k.pkgs=b);F(p,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=n(b))});if(a.deps||a.callback)i.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(Z,arguments));return b||a.exports&&ba(a.exports)}},makeRequire:function(a,f){function d(e,c,h){var g,k;f.enableBuildCallback&&(c&&H(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(H(c))return v(A("requireargs",
+"Invalid require call"),h);if(a&&s(N,e))return N[e](p[a.id]);if(j.get)return j.get(i,e,a,d);g=n(e,a,!1,!0);g=g.id;return!s(r,g)?v(A("notloaded",'Module name "'+g+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):r[g]}K();i.nextTick(function(){K();k=q(n(null,a));k.skipMap=f.skipMap;k.init(e,c,h,{enabled:!0});C()});return d}f=f||{};Q(d,{isBrowser:z,toUrl:function(b){var d,f=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==f&&(!("."===g||".."===g)||1h.attachEvent.toString().indexOf("[native code"))&&!W?(O=!0,h.attachEvent("onreadystatechange",b.onScriptLoad)):(h.addEventListener("load",b.onScriptLoad,!1),h.addEventListener("error",
+b.onScriptError,!1)),h.src=d,K=h,C?x.insertBefore(h,C):x.appendChild(h),K=null,h;if(da)try{importScripts(d),b.completeLoad(c)}catch(l){b.onError(A("importscripts","importScripts failed for "+c+" at "+d,l,[c]))}};z&&M(document.getElementsByTagName("script"),function(b){x||(x=b.parentNode);if(J=b.getAttribute("data-main"))return q=J,t.baseUrl||(D=q.split("/"),q=D.pop(),fa=D.length?D.join("/")+"/":"./",t.baseUrl=fa),q=q.replace(ea,""),j.jsExtRegExp.test(q)&&(q=J),t.deps=t.deps?t.deps.concat(q):[q],!0});
+define=function(b,c,d){var h,j;"string"!==typeof b&&(d=c,c=b,b=null);I(c)||(d=c,c=null);!c&&H(d)&&(c=[],d.length&&(d.toString().replace(la,"").replace(ma,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(O){if(!(h=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),h=P;h&&(b||(b=h.getAttribute("data-requiremodule")),j=E[h.getAttribute("data-requirecontext")])}(j?j.defQueue:
+R).push([b,c,d])};define.amd={jQuery:!0};j.exec=function(b){return eval(b)};j(t)}})(this);
diff --git a/common/static/js/vendor/timepicker/datepair.js b/common/static/js/vendor/timepicker/datepair.js
index cd1955ba04..e5cbf12b25 100644
--- a/common/static/js/vendor/timepicker/datepair.js
+++ b/common/static/js/vendor/timepicker/datepair.js
@@ -10,6 +10,8 @@ requires jQuery 1.6+
version: 1.2.2
************************/
+define(["jquery", "jquery.ui", "jquery.timepicker"], function($) {
+
$(function() {
$('.datepair input.date').each(function(){
@@ -73,7 +75,7 @@ $(function() {
if (target.val() == '') {
return;
}
-
+
var container = target.closest('.datepair');
if (target.hasClass('date')) {
@@ -206,4 +208,6 @@ $(function() {
});
// Simulates PHP's date function
-Date.prototype.format=function(format){var returnStr='';var replace=Date.replaceChars;for(var i=0;i tag in the test runner page.
-# When loading many files, this can be slow, so
+# When loading many files, this can be slow, so
# exclude any files you don't need.
#exclude_from_page:
-# - path/to/lib/exclude/*
+# - path/to/lib/include/exception_*.js
# Regular expression used to guarantee that a *.js file
# is included in the test runner page.
diff --git a/common/templates/courseware_vendor_js.html b/common/templates/courseware_vendor_js.html
deleted file mode 100644
index 20466210b8..0000000000
--- a/common/templates/courseware_vendor_js.html
+++ /dev/null
@@ -1,21 +0,0 @@
-<%namespace name='static' file='static_content.html'/>
-
-
-
-
-
-
-
-
-
-
-
-## codemirror
-
-
-
-## tiny_mce
-
-
-
-<%include file="mathjax_include.html" />
diff --git a/lms/djangoapps/courseware/features/navigation.py b/lms/djangoapps/courseware/features/navigation.py
index e4cd4961c2..18f66cc1d4 100644
--- a/lms/djangoapps/courseware/features/navigation.py
+++ b/lms/djangoapps/courseware/features/navigation.py
@@ -99,6 +99,7 @@ def click_on_sequence(step, sequence):
@step(u'I should see the content of (?:sub)?section "([^"]*)"$')
def see_section_content(step, section):
+ world.wait(0.5)
if section == "2":
text = 'The correct answer is Option 2'
elif section == "1":
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 3c1fe4e0e7..ab656a0923 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -377,6 +377,7 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
system = ModuleSystem(
track_function=track_function,
render_template=render_to_string,
+ static_url=settings.STATIC_URL,
ajax_url=ajax_url,
xqueue=xqueue,
# TODO (cpennington): Figure out how to share info between systems
diff --git a/lms/djangoapps/courseware/tests/load_tests/custom_response/test_scripts/v_user.py b/lms/djangoapps/courseware/tests/load_tests/custom_response/test_scripts/v_user.py
index 9bfc39e55b..5ffb24c02c 100644
--- a/lms/djangoapps/courseware/tests/load_tests/custom_response/test_scripts/v_user.py
+++ b/lms/djangoapps/courseware/tests/load_tests/custom_response/test_scripts/v_user.py
@@ -18,7 +18,7 @@ CACHE_SETTINGS = {
# Configure settings so Django will let us import its cache wrapper
# Caching is the only part of Django being tested
-from django.conf import settings
+from django.conf import settings
settings.configure(CACHES=CACHE_SETTINGS)
from django.core.cache import cache
@@ -32,6 +32,7 @@ TEST_SCRIPT = textwrap.dedent("""
# Submissions submitted by the student
TEST_SUBMISSIONS = [random.randint(-100, 100) for i in range(100)]
+
class TestContext(object):
""" One-time set up for the test that is shared across transactions.
Uses a Singleton design pattern."""
@@ -48,6 +49,7 @@ class TestContext(object):
# Create a mock ModuleSystem, installing our cache
system = mock.MagicMock(ModuleSystem)
+ system.STATIC_URL = '/dummy-static/'
system.render_template = lambda template, context: "%s" % template
system.cache = cache
system.filestore = mock.MagicMock(fs.osfs.OSFS)
@@ -86,6 +88,7 @@ class TestContext(object):
""" Return one of a small number of student submissions """
return random.choice(TEST_SUBMISSIONS)
+
class Transaction(object):
""" User script that submits a response to a CustomResponse problem """
@@ -95,15 +98,15 @@ class Transaction(object):
# Get the context (re-used across transactions)
self.context = TestContext.singleton()
- # Create a new custom response problem
+ # Create a new custom response problem
# using one of a small number of unique seeds
# We're assuming that the capa module is limiting the number
# of seeds (currently not the case for certain settings)
- self.problem = lcp.LoncapaProblem(self.context.xml,
- '1',
- state=None,
- seed=self.context.random_seed(),
- system=self.context.system)
+ self.problem = lcp.LoncapaProblem(
+ self.context.xml, '1',
+ state=None, seed=self.context.random_seed(),
+ system=self.context.system,
+ )
def run(self):
""" Submit a response to the CustomResponse problem """
diff --git a/lms/djangoapps/open_ended_grading/open_ended_notifications.py b/lms/djangoapps/open_ended_grading/open_ended_notifications.py
index 9cf7e76b23..00b9f7e866 100644
--- a/lms/djangoapps/open_ended_grading/open_ended_notifications.py
+++ b/lms/djangoapps/open_ended_grading/open_ended_notifications.py
@@ -126,6 +126,7 @@ def combined_notifications(course, user):
#Define a mock modulesystem
system = ModuleSystem(
+ static_url="/static",
ajax_url=None,
track_function=None,
get_module = None,
diff --git a/lms/djangoapps/open_ended_grading/staff_grading_service.py b/lms/djangoapps/open_ended_grading/staff_grading_service.py
index 526870f04d..dacf52b39e 100644
--- a/lms/djangoapps/open_ended_grading/staff_grading_service.py
+++ b/lms/djangoapps/open_ended_grading/staff_grading_service.py
@@ -70,6 +70,7 @@ class StaffGradingService(GradingService):
def __init__(self, config):
config['system'] = ModuleSystem(
+ static_url='/static',
ajax_url=None,
track_function=None,
get_module = None,
diff --git a/lms/djangoapps/open_ended_grading/tests.py b/lms/djangoapps/open_ended_grading/tests.py
index 5224386664..65a1ce68cc 100644
--- a/lms/djangoapps/open_ended_grading/tests.py
+++ b/lms/djangoapps/open_ended_grading/tests.py
@@ -35,6 +35,7 @@ from courseware.tests import factories
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from courseware.tests.helpers import LoginEnrollmentTestCase, check_for_get_code, check_for_post_code
+
class EmptyStaffGradingService(object):
"""
A staff grading service that does not return a problem list from get_problem_list.
@@ -47,6 +48,7 @@ class EmptyStaffGradingService(object):
"""
return json.dumps({'success': True, 'error': 'No problems found.'})
+
def make_instructor(course, user_email):
"""
Makes a given user an instructor in a course.
@@ -55,6 +57,7 @@ def make_instructor(course, user_email):
group = Group.objects.create(name=group_name)
group.user_set.add(User.objects.get(email=user_email))
+
class StudentProblemListMockQuery(object):
"""
Mock controller query service for testing student problem list functionality.
@@ -98,6 +101,7 @@ class StudentProblemListMockQuery(object):
)
return grading_status_list
+
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
class TestStaffGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
'''
@@ -241,6 +245,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
field_data = DictFieldData({'data': " ", 'location': location, 'category':'peergrading'})
self.mock_service = peer_grading_service.MockPeerGradingService()
self.system = ModuleSystem(
+ static_url=settings.STATIC_URL,
ajax_url=location,
track_function=None,
get_module=None,
@@ -408,6 +413,7 @@ class TestPanel(ModuleStoreTestCase):
response = views.student_problem_list(request, self.course.id)
self.assertRegexpMatches(response.content, "Here is a list of open ended problems for this course.")
+
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
class TestPeerGradingFound(ModuleStoreTestCase):
"""
@@ -427,6 +433,7 @@ class TestPeerGradingFound(ModuleStoreTestCase):
found, url = views.find_peer_grading_module(self.course)
self.assertEqual(found, False)
+
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
class TestStudentProblemList(ModuleStoreTestCase):
"""
@@ -464,5 +471,3 @@ class TestStudentProblemList(ModuleStoreTestCase):
self.assertEqual(len(valid_problems), 2)
# Ensure that human names are being set properly.
self.assertEqual(valid_problems[0]['grader_type_display_name'], "Instructor Assessment")
-
-
diff --git a/lms/djangoapps/open_ended_grading/utils.py b/lms/djangoapps/open_ended_grading/utils.py
index 1f860b1a6d..3ad85544f2 100644
--- a/lms/djangoapps/open_ended_grading/utils.py
+++ b/lms/djangoapps/open_ended_grading/utils.py
@@ -24,19 +24,21 @@ GRADER_DISPLAY_NAMES = {
'NA': _("Not yet available"),
'BC': _("Automatic Checker"),
'IN': _("Instructor Assessment"),
- }
+}
STUDENT_ERROR_MESSAGE = _("Error occurred while contacting the grading service. Please notify course staff.")
STAFF_ERROR_MESSAGE = _("Error occurred while contacting the grading service. Please notify your edX point of contact.")
system = ModuleSystem(
+ static_url='/static',
ajax_url=None,
track_function=None,
get_module=None,
render_template=render_to_string,
replace_urls=None,
xmodule_field_data=DictFieldData({}),
- )
+)
+
def generate_problem_url(problem_url_parts, base_course_url):
"""
@@ -53,6 +55,7 @@ def generate_problem_url(problem_url_parts, base_course_url):
problem_url += part + "/"
return problem_url
+
def does_location_exist(course_id, location):
"""
Checks to see if a valid module exists at a given location (ie has not been deleted)
@@ -74,6 +77,7 @@ def does_location_exist(course_id, location):
"Ensure that the location is valid.").format(location))
return False
+
def create_controller_query_service():
"""
Return an instance of a service that can query edX ORA.
@@ -81,6 +85,7 @@ def create_controller_query_service():
return ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE, system)
+
class StudentProblemList(object):
"""
Get a list of problems that the student has attempted from ORA.
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 03e8202d75..731c4f3c51 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -613,10 +613,9 @@ courseware_js = (
sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/modules/**/*.js'))
)
-# 'js/vendor/RequireJS.js' - Require JS wrapper.
-# See https://edx-wiki.atlassian.net/wiki/display/LMS/Integration+of+Require+JS+into+the+system
main_vendor_js = [
- 'js/vendor/RequireJS.js',
+ 'js/vendor/require.js',
+ 'js/RequireJS-namespace-undefine.js',
'js/vendor/json2.js',
'js/vendor/jquery.min.js',
'js/vendor/jquery-ui.min.js',
diff --git a/lms/static/coffee/spec/requirejs_spec.coffee b/lms/static/coffee/spec/requirejs_spec.coffee
index 10d34a2f75..2ecbc8959d 100644
--- a/lms/static/coffee/spec/requirejs_spec.coffee
+++ b/lms/static/coffee/spec/requirejs_spec.coffee
@@ -1,89 +1,89 @@
describe "RequireJS namespacing", ->
- beforeEach ->
+ beforeEach ->
- # Jasmine does not provide a way to use the typeof operator. We need
- # to create our own custom matchers so that a TypeError is not thrown.
- @addMatchers
- requirejsTobeUndefined: ->
- typeof requirejs is "undefined"
+ # Jasmine does not provide a way to use the typeof operator. We need
+ # to create our own custom matchers so that a TypeError is not thrown.
+ @addMatchers
+ requirejsTobeUndefined: ->
+ typeof requirejs is "undefined"
- requireTobeUndefined: ->
- typeof require is "undefined"
+ requireTobeUndefined: ->
+ typeof require is "undefined"
- defineTobeUndefined: ->
- typeof define is "undefined"
+ defineTobeUndefined: ->
+ typeof define is "undefined"
- it "check that the RequireJS object is present in the global namespace", ->
- expect(RequireJS).toEqual jasmine.any(Object)
- expect(window.RequireJS).toEqual jasmine.any(Object)
+ it "check that the RequireJS object is present in the global namespace", ->
+ expect(RequireJS).toEqual jasmine.any(Object)
+ expect(window.RequireJS).toEqual jasmine.any(Object)
- it "check that requirejs(), require(), and define() are not in the global namespace", ->
+ it "check that requirejs(), require(), and define() are not in the global namespace", ->
- # The custom matchers that we defined in the beforeEach() function do
- # not operate on an object. We pass a dummy empty object {} not to
- # confuse Jasmine.
- expect({}).requirejsTobeUndefined()
- expect({}).requireTobeUndefined()
- expect({}).defineTobeUndefined()
- expect(window.requirejs).not.toBeDefined()
- expect(window.require).not.toBeDefined()
- expect(window.define).not.toBeDefined()
+ # The custom matchers that we defined in the beforeEach() function do
+ # not operate on an object. We pass a dummy empty object {} not to
+ # confuse Jasmine.
+ expect({}).requirejsTobeUndefined()
+ expect({}).requireTobeUndefined()
+ expect({}).defineTobeUndefined()
+ expect(window.requirejs).not.toBeDefined()
+ expect(window.require).not.toBeDefined()
+ expect(window.define).not.toBeDefined()
describe "RequireJS module creation", ->
- inDefineCallback = undefined
- inRequireCallback = undefined
- it "check that we can use RequireJS to define() and require() a module", ->
+ inDefineCallback = undefined
+ inRequireCallback = undefined
+ it "check that we can use RequireJS to define() and require() a module", ->
- # Because Require JS works asynchronously when defining and requiring
- # modules, we need to use the special Jasmine functions runs(), and
- # waitsFor() to set up this test.
- runs ->
+ # Because Require JS works asynchronously when defining and requiring
+ # modules, we need to use the special Jasmine functions runs(), and
+ # waitsFor() to set up this test.
+ runs ->
- # Initialize the variable that we will test for. They will be set
- # to true in the appropriate callback functions called by Require
- # JS. If their values do not change, this will mean that something
- # is not working as is intended.
- inDefineCallback = false
- inRequireCallback = false
+ # Initialize the variable that we will test for. They will be set
+ # to true in the appropriate callback functions called by Require
+ # JS. If their values do not change, this will mean that something
+ # is not working as is intended.
+ inDefineCallback = false
+ inRequireCallback = false
- # Define our test module.
- RequireJS.define "test_module", [], ->
- inDefineCallback = true
+ # Define our test module.
+ RequireJS.define "test_module", [], ->
+ inDefineCallback = true
- # This module returns an object. It can be accessed via the
- # Require JS require() function.
- module_status: "OK"
+ # This module returns an object. It can be accessed via the
+ # Require JS require() function.
+ module_status: "OK"
- # Require our defined test module.
- RequireJS.require ["test_module"], (test_module) ->
- inRequireCallback = true
+ # Require our defined test module.
+ RequireJS.require ["test_module"], (test_module) ->
+ inRequireCallback = true
- # If our test module was defined properly, then we should
- # be able to get the object it returned, and query some
- # property.
- expect(test_module.module_status).toBe "OK"
+ # If our test module was defined properly, then we should
+ # be able to get the object it returned, and query some
+ # property.
+ expect(test_module.module_status).toBe "OK"
- # We will wait for a specified amount of time (1 second), before
- # checking if our module was defined and that we were able to
- # require() the module.
- waitsFor (->
+ # We will wait for a specified amount of time (1 second), before
+ # checking if our module was defined and that we were able to
+ # require() the module.
+ waitsFor (->
- # If at least one of the callback functions was not reached, we
- # fail this test.
- return false if (inDefineCallback isnt true) or (inRequireCallback isnt true)
+ # If at least one of the callback functions was not reached, we
+ # fail this test.
+ return false if (inDefineCallback isnt true) or (inRequireCallback isnt true)
- # Both of the callbacks were reached.
- true
- ), "We should eventually end up in the defined callback", 1000
+ # Both of the callbacks were reached.
+ true
+ ), "We should eventually end up in the defined callback", 1000
- # The final test behavior, after waitsFor() finishes waiting.
- runs ->
- expect(inDefineCallback).toBeTruthy()
- expect(inRequireCallback).toBeTruthy()
+ # The final test behavior, after waitsFor() finishes waiting.
+ runs ->
+ expect(inDefineCallback).toBeTruthy()
+ expect(inRequireCallback).toBeTruthy()
diff --git a/lms/static/js/RequireJS-namespace-undefine.js b/lms/static/js/RequireJS-namespace-undefine.js
new file mode 120000
index 0000000000..e62b9db026
--- /dev/null
+++ b/lms/static/js/RequireJS-namespace-undefine.js
@@ -0,0 +1 @@
+../../../common/static/js/RequireJS-namespace-undefine.js
\ No newline at end of file
diff --git a/lms/static/js_test.yml b/lms/static/js_test.yml
index 92197575b6..b884b7bccd 100644
--- a/lms/static/js_test.yml
+++ b/lms/static/js_test.yml
@@ -30,22 +30,17 @@ prepend_path: lms/static
lib_paths:
- xmodule_js/common_static/coffee/src/ajax_prefix.js
- xmodule_js/common_static/coffee/src/logger.js
- - xmodule_js/common_static/js/vendor/RequireJS.js
- - xmodule_js/common_static/js/vendor/json2.js
+ - xmodule_js/common_static/js/vendor/jasmine-jquery.js
+ - xmodule_js/common_static/js/vendor/require.js
+ - js/RequireJS-namespace-undefine.js
- xmodule_js/common_static/js/vendor/jquery.min.js
- xmodule_js/common_static/js/vendor/jquery-ui.min.js
- xmodule_js/common_static/js/vendor/jquery.cookie.js
- - xmodule_js/common_static/js/vendor/jquery.qtip.min.js
- - xmodule_js/common_static/js/vendor/swfobject/swfobject.js
- - xmodule_js/common_static/js/vendor/jquery.ba-bbq.min.js
- - xmodule_js/common_static/js/vendor/annotator.min.js
- - xmodule_js/common_static/js/vendor/annotator.store.min.js
- - xmodule_js/common_static/js/vendor/annotator.tags.min.js
- - xmodule_js/common_static/js/vendor/jasmine-jquery.js
- - xmodule_js/common_static/js/vendor/jquery.leanModal.min.js
- xmodule_js/common_static/js/vendor/flot/jquery.flot.js
- - xmodule_js/src
- - xmodule_js/common_static/js/test/add_ajax_prefix.js
+ - xmodule_js/common_static/js/vendor/CodeMirror/codemirror.js
+ - xmodule_js/src/capa/
+ - xmodule_js/src/video/
+ - xmodule_js/src/xmodule.js
# Paths to source JavaScript files
src_paths:
@@ -54,7 +49,6 @@ src_paths:
# Paths to spec (test) JavaScript files
spec_paths:
- - coffee/spec/helper.js
- coffee/spec
# Paths to fixture files (optional)
@@ -73,7 +67,7 @@ fixture_paths:
# appearing in the test runner page.
# Files are included by default, which means that they
# are loaded using a