diff --git a/.jshintignore b/.jshintignore
index 26e4c15e1e439d5b429536116e93c9946fa4d135..0065f2da20e4648c1e355d2ffcf010a2f3e6b7a5 100644
--- a/.jshintignore
+++ b/.jshintignore
@@ -1,4 +1,6 @@
 **/vendor
+cms/static/cms/js/build.js
+cms/static/cms/js/spec/main.js
 cms/static/js/i18n/**/*.js
 lms/static/js/i18n/**/*.js
 lms/static/lms/js/build.js
diff --git a/cms/static/cms/js/build.js b/cms/static/cms/js/build.js
index 65e78c3e62a2a6d98bfb356a25fa055c547e4997..f6a3c1534c80c0f1258a63540b10595881231508 100644
--- a/cms/static/cms/js/build.js
+++ b/cms/static/cms/js/build.js
@@ -1,8 +1,8 @@
-(function () {
+(function() {
     'use strict';
     var commonLibrariesPath = 'common/js/common_libraries';
 
-    var getModule = function (moduleName, excludeCommonDeps) {
+    var getModule = function(moduleName, excludeCommonDeps) {
         var module = {
             name: moduleName
         };
@@ -14,7 +14,7 @@
         return module;
     };
 
-    var getModulesList = function (modules) {
+    var getModulesList = function(modules) {
         var result = [getModule(commonLibrariesPath)];
         return result.concat(modules.map(function (moduleName) {
             return getModule(moduleName, true);
@@ -92,7 +92,7 @@
         /**
          * Stub out requireJS text in the optimized file, but leave available for non-optimized development use.
          */
-        stubModules: ["text"],
+        stubModules: ['text'],
 
         /**
          * If shim config is used in the app during runtime, duplicate the config
@@ -170,4 +170,4 @@
          */
         logLevel: 1
     };
-} ())
+}())
diff --git a/cms/static/cms/js/require-config.js b/cms/static/cms/js/require-config.js
index 70e8d61e0b0fe9ad1f570595555721b8182e9fab..7cd1f1caf1e1c0211768638acf3c9f1ff5d7ba2c 100644
--- a/cms/static/cms/js/require-config.js
+++ b/cms/static/cms/js/require-config.js
@@ -1,5 +1,6 @@
-;(function (require, define) {
+;(function(require, define) {
     'use strict';
+
     if (window) {
         // MathJax Fast Preview was introduced in 2.5. However, it
         // causes undesirable flashing/font size changes when
@@ -16,300 +17,300 @@
         // needs to be served. To handle this, we load the correct file in the
         // rendered template and then use this to ensure that RequireJS knows
         // how to find it.
-        define("gettext", function () { return window.gettext; });
+        define('gettext', function() { return window.gettext; });
     }
 
     require.config({
         // NOTE: baseUrl has been previously set in cms/static/templates/base.html
         waitSeconds: 60,
         paths: {
-            "domReady": "js/vendor/domReady",
-            "mustache": "js/vendor/mustache",
-            "codemirror": "js/vendor/codemirror-compressed",
-            "codemirror/stex": "js/vendor/CodeMirror/stex",
-            "jquery": "common/js/vendor/jquery",
-            "jquery-migrate": "common/js/vendor/jquery-migrate",
-            "jquery.ui": "js/vendor/jquery-ui.min",
-            "jquery.form": "js/vendor/jquery.form",
-            "jquery.markitup": "js/vendor/markitup/jquery.markitup",
-            "jquery.leanModal": "js/vendor/jquery.leanModal",
-            "jquery.ajaxQueue": "js/vendor/jquery.ajaxQueue",
-            "jquery.smoothScroll": "js/vendor/jquery.smooth-scroll.min",
-            "jquery.timepicker": "js/vendor/timepicker/jquery.timepicker",
-            "jquery.cookie": "js/vendor/jquery.cookie",
-            "jquery.qtip": "js/vendor/jquery.qtip.min",
-            "jquery.scrollTo": "common/js/vendor/jquery.scrollTo",
-            "jquery.flot": "js/vendor/flot/jquery.flot.min",
-            "jquery.fileupload": "js/vendor/jQuery-File-Upload/js/jquery.fileupload",
-            "jquery.fileupload-process": "js/vendor/jQuery-File-Upload/js/jquery.fileupload-process",
-            "jquery.fileupload-validate": "js/vendor/jQuery-File-Upload/js/jquery.fileupload-validate",
-            "jquery.iframe-transport": "js/vendor/jQuery-File-Upload/js/jquery.iframe-transport",
-            "jquery.inputnumber": "js/vendor/html5-input-polyfills/number-polyfill",
-            "jquery.immediateDescendents": "coffee/src/jquery.immediateDescendents",
-            "datepair": "js/vendor/timepicker/datepair",
-            "date": "js/vendor/date",
-            "moment": "js/vendor/moment.min",
-            "moment-with-locales": "js/vendor/moment-with-locales.min",
-            "text": 'js/vendor/requirejs/text',
-            "underscore": "common/js/vendor/underscore",
-            "underscore.string": "common/js/vendor/underscore.string",
-            "backbone": "common/js/vendor/backbone",
-            "backbone-relational" : "js/vendor/backbone-relational.min",
-            "backbone.associations": "js/vendor/backbone-associations-min",
-            "backbone.paginator": "common/js/vendor/backbone.paginator",
-            "tinymce": "js/vendor/tinymce/js/tinymce/tinymce.full.min",
-            "jquery.tinymce": "js/vendor/tinymce/js/tinymce/jquery.tinymce.min",
-            "xmodule": "/xmodule/xmodule",
-            "xblock/core": "js/xblock/core",
-            "xblock": "coffee/src/xblock",
-            "utility": "js/src/utility",
-            "accessibility": "js/src/accessibility_tools",
-            "URI": "js/vendor/URI.min",
-            "ieshim": "js/src/ie_shim",
-            "tooltip_manager": "js/src/tooltip_manager",
-            "modernizr": "edx-pattern-library/js/modernizr-custom",
-            "afontgarde": "edx-pattern-library/js/afontgarde",
-            "edxicons": "edx-pattern-library/js/edx-icons",
-            "draggabilly": "js/vendor/draggabilly",
+            'domReady': 'js/vendor/domReady',
+            'mustache': 'js/vendor/mustache',
+            'codemirror': 'js/vendor/codemirror-compressed',
+            'codemirror/stex': 'js/vendor/CodeMirror/stex',
+            'jquery': 'common/js/vendor/jquery',
+            'jquery-migrate': 'common/js/vendor/jquery-migrate',
+            'jquery.ui': 'js/vendor/jquery-ui.min',
+            'jquery.form': 'js/vendor/jquery.form',
+            'jquery.markitup': 'js/vendor/markitup/jquery.markitup',
+            'jquery.leanModal': 'js/vendor/jquery.leanModal',
+            'jquery.ajaxQueue': 'js/vendor/jquery.ajaxQueue',
+            'jquery.smoothScroll': 'js/vendor/jquery.smooth-scroll.min',
+            'jquery.timepicker': 'js/vendor/timepicker/jquery.timepicker',
+            'jquery.cookie': 'js/vendor/jquery.cookie',
+            'jquery.qtip': 'js/vendor/jquery.qtip.min',
+            'jquery.scrollTo': 'common/js/vendor/jquery.scrollTo',
+            'jquery.flot': 'js/vendor/flot/jquery.flot.min',
+            'jquery.fileupload': 'js/vendor/jQuery-File-Upload/js/jquery.fileupload',
+            'jquery.fileupload-process': 'js/vendor/jQuery-File-Upload/js/jquery.fileupload-process',
+            'jquery.fileupload-validate': 'js/vendor/jQuery-File-Upload/js/jquery.fileupload-validate',
+            'jquery.iframe-transport': 'js/vendor/jQuery-File-Upload/js/jquery.iframe-transport',
+            'jquery.inputnumber': 'js/vendor/html5-input-polyfills/number-polyfill',
+            'jquery.immediateDescendents': 'coffee/src/jquery.immediateDescendents',
+            'datepair': 'js/vendor/timepicker/datepair',
+            'date': 'js/vendor/date',
+            'moment': 'js/vendor/moment.min',
+            'moment-with-locales': 'js/vendor/moment-with-locales.min',
+            'text': 'js/vendor/requirejs/text',
+            'underscore': 'common/js/vendor/underscore',
+            'underscore.string': 'common/js/vendor/underscore.string',
+            'backbone': 'common/js/vendor/backbone',
+            'backbone-relational': 'js/vendor/backbone-relational.min',
+            'backbone.associations': 'js/vendor/backbone-associations-min',
+            'backbone.paginator': 'common/js/vendor/backbone.paginator',
+            'tinymce': 'js/vendor/tinymce/js/tinymce/tinymce.full.min',
+            'jquery.tinymce': 'js/vendor/tinymce/js/tinymce/jquery.tinymce.min',
+            'xmodule': '/xmodule/xmodule',
+            'xblock/cms.runtime.v1': 'cms/js/xblock/cms.runtime.v1',
+            'xblock': 'common/js/xblock',
+            'utility': 'js/src/utility',
+            'accessibility': 'js/src/accessibility_tools',
+            'URI': 'js/vendor/URI.min',
+            'ieshim': 'js/src/ie_shim',
+            'tooltip_manager': 'js/src/tooltip_manager',
+            'modernizr': 'edx-pattern-library/js/modernizr-custom',
+            'afontgarde': 'edx-pattern-library/js/afontgarde',
+            'edxicons': 'edx-pattern-library/js/edx-icons',
+            'draggabilly': 'js/vendor/draggabilly',
 
             // Files needed for Annotations feature
-            "annotator": "js/vendor/ova/annotator-full",
-            "annotator-harvardx": "js/vendor/ova/annotator-full-firebase-auth",
-            "video.dev": "js/vendor/ova/video.dev",
-            "vjs.youtube": 'js/vendor/ova/vjs.youtube',
-            "rangeslider": 'js/vendor/ova/rangeslider',
-            "share-annotator": 'js/vendor/ova/share-annotator',
-            "richText-annotator": 'js/vendor/ova/richText-annotator',
-            "reply-annotator": 'js/vendor/ova/reply-annotator',
-            "grouping-annotator": 'js/vendor/ova/grouping-annotator',
-            "tags-annotator": 'js/vendor/ova/tags-annotator',
-            "diacritic-annotator": 'js/vendor/ova/diacritic-annotator',
-            "flagging-annotator": 'js/vendor/ova/flagging-annotator',
-            "jquery-Watch": 'js/vendor/ova/jquery-Watch',
-            "openseadragon": 'js/vendor/ova/openseadragon',
-            "osda": 'js/vendor/ova/OpenSeaDragonAnnotation',
-            "ova": 'js/vendor/ova/ova',
-            "catch": 'js/vendor/ova/catch/js/catch',
-            "handlebars": 'js/vendor/ova/catch/js/handlebars-1.1.2',
-            "lang_edx": "js/src/lang_edx",
+            'annotator': 'js/vendor/ova/annotator-full',
+            'annotator-harvardx': 'js/vendor/ova/annotator-full-firebase-auth',
+            'video.dev': 'js/vendor/ova/video.dev',
+            'vjs.youtube': 'js/vendor/ova/vjs.youtube',
+            'rangeslider': 'js/vendor/ova/rangeslider',
+            'share-annotator': 'js/vendor/ova/share-annotator',
+            'richText-annotator': 'js/vendor/ova/richText-annotator',
+            'reply-annotator': 'js/vendor/ova/reply-annotator',
+            'grouping-annotator': 'js/vendor/ova/grouping-annotator',
+            'tags-annotator': 'js/vendor/ova/tags-annotator',
+            'diacritic-annotator': 'js/vendor/ova/diacritic-annotator',
+            'flagging-annotator': 'js/vendor/ova/flagging-annotator',
+            'jquery-Watch': 'js/vendor/ova/jquery-Watch',
+            'openseadragon': 'js/vendor/ova/openseadragon',
+            'osda': 'js/vendor/ova/OpenSeaDragonAnnotation',
+            'ova': 'js/vendor/ova/ova',
+            'catch': 'js/vendor/ova/catch/js/catch',
+            'handlebars': 'js/vendor/ova/catch/js/handlebars-1.1.2',
+            'lang_edx': 'js/src/lang_edx',
             // end of Annotation tool files
 
             // externally hosted files
-            "mathjax": "//cdn.mathjax.org/mathjax/2.6-latest/MathJax.js?config=TeX-MML-AM_SVG&delayStartupUntil=configured", // jshint ignore:line
-            "youtube": [
-                // youtube URL does not end in ".js". We add "?noext" to the path so
-                // that require.js adds the ".js" to the query component of the URL,
+            'mathjax': '//cdn.mathjax.org/mathjax/2.6-latest/MathJax.js?config=TeX-MML-AM_SVG&delayStartupUntil=configured', // jshint ignore:line
+            'youtube': [
+                // youtube URL does not end in '.js'. We add '?noext' to the path so
+                // that require.js adds the '.js' to the query component of the URL,
                 // and leaves the path component intact.
-                "//www.youtube.com/player_api?noext",
+                '//www.youtube.com/player_api?noext',
                 // if youtube fails to load, fallback on a local file
                 // so that require doesn't fall over
-                "js/src/youtube_fallback"
+                'js/src/youtube_fallback'
             ]
         },
         shim: {
-            "gettext": {
-                exports: "gettext"
+            'gettext': {
+                exports: 'gettext'
             },
-            "date": {
-                exports: "Date"
+            'date': {
+                exports: 'Date'
             },
-            "jquery-migrate": ['jquery'],
-            "jquery.ui": {
-                deps: ["jquery"],
-                exports: "jQuery.ui"
+            'jquery-migrate': ['jquery'],
+            'jquery.ui': {
+                deps: ['jquery'],
+                exports: 'jQuery.ui'
             },
-            "jquery.form": {
-                deps: ["jquery"],
-                exports: "jQuery.fn.ajaxForm"
+            'jquery.form': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.ajaxForm'
             },
-            "jquery.markitup": {
-                deps: ["jquery"],
-                exports: "jQuery.fn.markitup"
+            'jquery.markitup': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.markitup'
             },
-            "jquery.leanmodal": {
-                deps: ["jquery"],
-                exports: "jQuery.fn.leanModal"
+            'jquery.leanmodal': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.leanModal'
             },
-            "jquery.ajaxQueue": {
-                deps: ["jquery"],
-                exports: "jQuery.fn.ajaxQueue"
+            'jquery.ajaxQueue': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.ajaxQueue'
             },
-            "jquery.smoothScroll": {
-                deps: ["jquery"],
-                exports: "jQuery.fn.smoothScroll"
+            'jquery.smoothScroll': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.smoothScroll'
             },
-            "jquery.cookie": {
-                deps: ["jquery"],
-                exports: "jQuery.fn.cookie"
+            'jquery.cookie': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.cookie'
             },
-            "jquery.qtip": {
-                deps: ["jquery"],
-                exports: "jQuery.fn.qtip"
+            'jquery.qtip': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.qtip'
             },
-            "jquery.scrollTo": {
-                deps: ["jquery"],
-                exports: "jQuery.fn.scrollTo"
+            'jquery.scrollTo': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.scrollTo'
             },
-            "jquery.flot": {
-                deps: ["jquery"],
-                exports: "jQuery.fn.plot"
+            'jquery.flot': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.plot'
             },
-            "jquery.fileupload": {
-                deps: ["jquery.ui", "jquery.iframe-transport"],
-                exports: "jQuery.fn.fileupload"
+            'jquery.fileupload': {
+                deps: ['jquery.ui', 'jquery.iframe-transport'],
+                exports: 'jQuery.fn.fileupload'
             },
-            "jquery.fileupload-process": {
-                deps: ["jquery.fileupload"]
+            'jquery.fileupload-process': {
+                deps: ['jquery.fileupload']
             },
-            "jquery.fileupload-validate": {
-                deps: ["jquery.fileupload"]
+            'jquery.fileupload-validate': {
+                deps: ['jquery.fileupload']
             },
-            "jquery.inputnumber": {
-                deps: ["jquery"],
-                exports: "jQuery.fn.inputNumber"
+            'jquery.inputnumber': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.inputNumber'
             },
-            "jquery.tinymce": {
-                deps: ["jquery", "tinymce"],
-                exports: "jQuery.fn.tinymce"
+            'jquery.tinymce': {
+                deps: ['jquery', 'tinymce'],
+                exports: 'jQuery.fn.tinymce'
             },
-            "datepair": {
-                deps: ["jquery.ui", "jquery.timepicker"]
+            'datepair': {
+                deps: ['jquery.ui', 'jquery.timepicker']
             },
-            "underscore": {
-                exports: "_"
+            'underscore': {
+                exports: '_'
             },
-            "backbone": {
-                deps: ["underscore", "jquery"],
-                exports: "Backbone"
+            'backbone': {
+                deps: ['underscore', 'jquery'],
+                exports: 'Backbone'
             },
-            "backbone.associations": {
-                deps: ["backbone"],
-                exports: "Backbone.Associations"
+            'backbone.associations': {
+                deps: ['backbone'],
+                exports: 'Backbone.Associations'
             },
-            "backbone.paginator": {
-                deps: ["backbone"],
-                exports: "Backbone.PageableCollection"
+            'backbone.paginator': {
+                deps: ['backbone'],
+                exports: 'Backbone.PageableCollection'
             },
-            "youtube": {
-                exports: "YT"
+            'youtube': {
+                exports: 'YT'
             },
-            "codemirror": {
-                exports: "CodeMirror"
+            'codemirror': {
+                exports: 'CodeMirror'
             },
-            "codemirror/stex": {
-                deps: ["codemirror"]
+            'codemirror/stex': {
+                deps: ['codemirror']
             },
-            "tinymce": {
-                exports: "tinymce"
+            'tinymce': {
+                exports: 'tinymce'
             },
-            "lang_edx": {
-                deps: ["jquery"]
+            'lang_edx': {
+                deps: ['jquery']
             },
-            "mathjax": {
-                exports: "MathJax",
+            'mathjax': {
+                exports: 'MathJax',
                 init: function() {
                     window.MathJax.Hub.Config({
                         tex2jax: {
                             inlineMath: [
-                                ["\\(","\\)"],
+                                ['\\(','\\)'],
                                 ['[mathjaxinline]','[/mathjaxinline]']
                             ],
                             displayMath: [
-                                ["\\[","\\]"],
+                                ['\\[','\\]'],
                                 ['[mathjax]','[/mathjax]']
                             ]
                         }
-                });
-                // In order to eliminate all flashing during interactive
-                // preview, it is necessary to set processSectionDelay to 0
-                // (remove delay between input and output phases). This
-                // effectively disables fast preview, regardless of
-                // the fast preview setting as shown in the context menu.
-                window.MathJax.Hub.processSectionDelay = 0;
-                window.MathJax.Hub.Configured();
+                    });
+                    // In order to eliminate all flashing during interactive
+                    // preview, it is necessary to set processSectionDelay to 0
+                    // (remove delay between input and output phases). This
+                    // effectively disables fast preview, regardless of
+                    // the fast preview setting as shown in the context menu.
+                    window.MathJax.Hub.processSectionDelay = 0;
+                    window.MathJax.Hub.Configured();
                 }
             },
-            "URI": {
-                exports: "URI"
+            'URI': {
+                exports: 'URI'
             },
-            "tooltip_manager": {
-                deps: ["jquery", "underscore"]
+            'tooltip_manager': {
+                deps: ['jquery', 'underscore']
             },
-            "jquery.immediateDescendents": {
-                deps: ["jquery"]
+            'jquery.immediateDescendents': {
+                deps: ['jquery']
             },
-            "xblock/core": {
-                exports: "XBlock",
-                deps: ["jquery", "jquery.immediateDescendents"]
+            'xblock/core': {
+                exports: 'XBlock',
+                deps: ['jquery', 'jquery.immediateDescendents']
             },
-            "xblock/runtime.v1": {
-                exports: "XBlock",
-                deps: ["xblock/core"]
+            'xblock/runtime.v1': {
+                exports: 'XBlock',
+                deps: ['xblock/core']
             },
-            "coffee/src/main": {
-                deps: ["coffee/src/ajax_prefix"]
+            'coffee/src/main': {
+                deps: ['coffee/src/ajax_prefix']
             },
-            "js/src/logger": {
-                exports: "Logger",
-                deps: ["coffee/src/ajax_prefix"]
+            'js/src/logger': {
+                exports: 'Logger',
+                deps: ['coffee/src/ajax_prefix']
             },
-            "modernizr": {
-                exports: "Modernizr"
+            'modernizr': {
+                exports: 'Modernizr'
             },
-            "afontgarde": {
-                exports: "AFontGarde"
+            'afontgarde': {
+                exports: 'AFontGarde'
             },
 
             // the following are all needed for annotation tools
-            "video.dev": {
-                exports:"videojs"
+            'video.dev': {
+                exports: 'videojs'
             },
-            "vjs.youtube": {
-                deps: ["video.dev"]
+            'vjs.youtube': {
+                deps: ['video.dev']
             },
-            "rangeslider": {
-                deps: ["video.dev"]
+            'rangeslider': {
+                deps: ['video.dev']
             },
-            "annotator": {
-                exports: "Annotator"
+            'annotator': {
+                exports: 'Annotator'
             },
-            "annotator-harvardx":{
-                deps: ["annotator"]
+            'annotator-harvardx': {
+                deps: ['annotator']
             },
-            "share-annotator": {
-                deps: ["annotator"]
+            'share-annotator': {
+                deps: ['annotator']
             },
-            "richText-annotator": {
-                deps: ["annotator", "tinymce"]
+            'richText-annotator': {
+                deps: ['annotator', 'tinymce']
             },
-            "reply-annotator": {
-                deps: ["annotator"]
+            'reply-annotator': {
+                deps: ['annotator']
             },
-            "tags-annotator": {
-                deps: ["annotator"]
+            'tags-annotator': {
+                deps: ['annotator']
             },
-            "diacritic-annotator": {
-                deps: ["annotator"]
+            'diacritic-annotator': {
+                deps: ['annotator']
             },
-            "flagging-annotator": {
-                deps: ["annotator"]
+            'flagging-annotator': {
+                deps: ['annotator']
             },
-            "grouping-annotator": {
-                deps: ["annotator"]
+            'grouping-annotator': {
+                deps: ['annotator']
             },
-            "ova":{
-                exports: "ova",
-                deps: ["annotator", "annotator-harvardx", "video.dev", "vjs.youtube",
-                       "rangeslider", "share-annotator", "richText-annotator", "reply-annotator",
-                       "tags-annotator", "flagging-annotator", "grouping-annotator", "diacritic-annotator",
-                       "jquery-Watch", "catch", "handlebars", "URI"]
+            'ova': {
+                exports: 'ova',
+                deps: ['annotator', 'annotator-harvardx', 'video.dev', 'vjs.youtube',
+                    'rangeslider', 'share-annotator', 'richText-annotator', 'reply-annotator',
+                    'tags-annotator', 'flagging-annotator', 'grouping-annotator', 'diacritic-annotator',
+                    'jquery-Watch', 'catch', 'handlebars', 'URI']
             },
-            "osda":{
-                exports: "osda",
-                deps: ["annotator", "annotator-harvardx", "video.dev", "vjs.youtube",
-                       "rangeslider", "share-annotator", "richText-annotator", "reply-annotator",
-                       "tags-annotator", "flagging-annotator", "grouping-annotator", "diacritic-annotator",
-                       "openseadragon", "jquery-Watch", "catch", "handlebars", "URI"]
+            'osda': {
+                exports: 'osda',
+                deps: ['annotator', 'annotator-harvardx', 'video.dev', 'vjs.youtube',
+                    'rangeslider', 'share-annotator', 'richText-annotator', 'reply-annotator',
+                    'tags-annotator', 'flagging-annotator', 'grouping-annotator', 'diacritic-annotator',
+                    'openseadragon', 'jquery-Watch', 'catch', 'handlebars', 'URI']
             }
             // end of annotation tool files
         }
diff --git a/cms/static/cms/js/spec/main.js b/cms/static/cms/js/spec/main.js
new file mode 100644
index 0000000000000000000000000000000000000000..076c47376c0f842c102789e4153bd046603cf12c
--- /dev/null
+++ b/cms/static/cms/js/spec/main.js
@@ -0,0 +1,299 @@
+(function(requirejs, requireSerial) {
+    'use strict';
+
+    var i, specHelpers, testFiles;
+
+    requirejs.config({
+        baseUrl: '/base/',
+        paths: {
+            'gettext': 'xmodule_js/common_static/js/test/i18n',
+            'mustache': 'xmodule_js/common_static/js/vendor/mustache',
+            'codemirror': 'xmodule_js/common_static/js/vendor/CodeMirror/codemirror',
+            'jquery': 'xmodule_js/common_static/common/js/vendor/jquery',
+            'jquery-migrate': 'xmodule_js/common_static/common/js/vendor/jquery-migrate',
+            'jquery.ui': 'xmodule_js/common_static/js/vendor/jquery-ui.min',
+            'jquery.form': 'xmodule_js/common_static/js/vendor/jquery.form',
+            'jquery.markitup': 'xmodule_js/common_static/js/vendor/markitup/jquery.markitup',
+            'jquery.leanModal': 'xmodule_js/common_static/js/vendor/jquery.leanModal',
+            'jquery.ajaxQueue': 'xmodule_js/common_static/js/vendor/jquery.ajaxQueue',
+            'jquery.smoothScroll': 'xmodule_js/common_static/js/vendor/jquery.smooth-scroll.min',
+            'jquery.scrollTo': 'common/js/vendor/jquery.scrollTo',
+            'jquery.timepicker': 'xmodule_js/common_static/js/vendor/timepicker/jquery.timepicker',
+            'jquery.cookie': 'xmodule_js/common_static/js/vendor/jquery.cookie',
+            'jquery.qtip': 'xmodule_js/common_static/js/vendor/jquery.qtip.min',
+            'jquery.fileupload': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload',
+            'jquery.fileupload-process': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload-process',  // jshint ignore:line
+            'jquery.fileupload-validate': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload-validate',  // jshint ignore:line
+            'jquery.iframe-transport': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.iframe-transport',  // jshint ignore:line
+            'jquery.inputnumber': 'xmodule_js/common_static/js/vendor/html5-input-polyfills/number-polyfill',
+            'jquery.immediateDescendents': 'xmodule_js/common_static/coffee/src/jquery.immediateDescendents',
+            'jquery.simulate': 'xmodule_js/common_static/js/vendor/jquery.simulate',
+            'datepair': 'xmodule_js/common_static/js/vendor/timepicker/datepair',
+            'date': 'xmodule_js/common_static/js/vendor/date',
+            'moment': 'xmodule_js/common_static/js/vendor/moment.min',
+            'moment-with-locales': 'xmodule_js/common_static/js/vendor/moment-with-locales.min',
+            'text': 'xmodule_js/common_static/js/vendor/requirejs/text',
+            'underscore': 'common/js/vendor/underscore',
+            'underscore.string': 'common/js/vendor/underscore.string',
+            'backbone': 'common/js/vendor/backbone',
+            'backbone.associations': 'xmodule_js/common_static/js/vendor/backbone-associations-min',
+            'backbone.paginator': 'common/js/vendor/backbone.paginator',
+            'backbone-relational': 'xmodule_js/common_static/js/vendor/backbone-relational.min',
+            'tinymce': 'xmodule_js/common_static/js/vendor/tinymce/js/tinymce/tinymce.full.min',
+            'jquery.tinymce': 'xmodule_js/common_static/js/vendor/tinymce/js/tinymce/jquery.tinymce',
+            'xmodule': 'xmodule_js/src/xmodule',
+            'xblock/cms.runtime.v1': 'cms/js/xblock/cms.runtime.v1',
+            'xblock': 'common/js/xblock',
+            'utility': 'xmodule_js/common_static/js/src/utility',
+            'accessibility': 'xmodule_js/common_static/js/src/accessibility_tools',
+            'sinon': 'xmodule_js/common_static/js/vendor/sinon-1.17.0',
+            'squire': 'xmodule_js/common_static/js/vendor/Squire',
+            'jasmine-imagediff': 'xmodule_js/common_static/js/vendor/jasmine-imagediff',
+            'draggabilly': 'xmodule_js/common_static/js/vendor/draggabilly',
+            'domReady': 'xmodule_js/common_static/js/vendor/domReady',
+            'URI': 'xmodule_js/common_static/js/vendor/URI.min',
+            'mock-ajax': 'xmodule_js/common_static/js/vendor/mock-ajax',
+            'modernizr': 'edx-pattern-library/js/modernizr-custom',
+            'afontgarde': 'edx-pattern-library/js/afontgarde',
+            'edxicons': 'edx-pattern-library/js/edx-icons',
+            'mathjax': '//cdn.mathjax.org/mathjax/2.6-latest/MathJax.js?config=TeX-MML-AM_SVG&delayStartupUntil=configured',  // jshint ignore:line
+            'youtube': '//www.youtube.com/player_api?noext',
+            'coffee/src/ajax_prefix': 'xmodule_js/common_static/coffee/src/ajax_prefix',
+            'js/spec/test_utils': 'js/spec/test_utils'
+        },
+        shim: {
+            'gettext': {
+                exports: 'gettext'
+            },
+            'date': {
+                exports: 'Date'
+            },
+            'jquery-migrate': ['jquery'],
+            'jquery.ui': {
+                deps: ['jquery'],
+                exports: 'jQuery.ui'
+            },
+            'jquery.form': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.ajaxForm'
+            },
+            'jquery.markitup': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.markitup'
+            },
+            'jquery.leanModal': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.leanModal'
+            },
+            'jquery.smoothScroll': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.smoothScroll'
+            },
+            'jquery.ajaxQueue': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.ajaxQueue'
+            },
+            'jquery.scrollTo': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.scrollTo'
+            },
+            'jquery.cookie': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.cookie'
+            },
+            'jquery.qtip': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.qtip'
+            },
+            'jquery.fileupload': {
+                deps: ['jquery.ui', 'jquery.iframe-transport'],
+                exports: 'jQuery.fn.fileupload'
+            },
+            'jquery.fileupload-process': {
+                deps: ['jquery.fileupload']
+            },
+            'jquery.fileupload-validate': {
+                deps: ['jquery.fileupload']
+            },
+            'jquery.inputnumber': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.inputNumber'
+            },
+            'jquery.simulate': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.simulate'
+            },
+            'jquery.tinymce': {
+                deps: ['jquery', 'tinymce'],
+                exports: 'jQuery.fn.tinymce'
+            },
+            'datepair': {
+                deps: ['jquery.ui', 'jquery.timepicker']
+            },
+            'underscore': {
+                exports: '_'
+            },
+            'backbone': {
+                deps: ['underscore', 'jquery'],
+                exports: 'Backbone'
+            },
+            'backbone.associations': {
+                deps: ['backbone'],
+                exports: 'Backbone.Associations'
+            },
+            'backbone.paginator': {
+                deps: ['backbone'],
+                exports: 'Backbone.PageableCollection'
+            },
+            'backbone-relational': {
+                deps: ['backbone']
+            },
+            'youtube': {
+                exports: 'YT'
+            },
+            'codemirror': {
+                exports: 'CodeMirror'
+            },
+            'tinymce': {
+                exports: 'tinymce'
+            },
+            'mathjax': {
+                exports: 'MathJax',
+                init: function() {
+                    window.MathJax.Hub.Config({
+                        tex2jax: {
+                            inlineMath: [['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]']],
+                            displayMath: [['\\[', '\\]'], ['[mathjax]', '[/mathjax]']]
+                        }
+                    });
+                    return window.MathJax.Hub.Configured();
+                }
+            },
+            'URI': {
+                exports: 'URI'
+            },
+            'xmodule': {
+                exports: 'XModule'
+            },
+            'sinon': {
+                exports: 'sinon'
+            },
+            'jasmine-imagediff': {},
+            'common/js/spec_helpers/jasmine-extensions': {
+                deps: ['jquery']
+            },
+            'common/js/spec_helpers/jasmine-stealth': {
+                deps: ['underscore', 'underscore.string']
+            },
+            'common/js/spec_helpers/jasmine-waituntil': {
+                deps: ['jquery']
+            },
+            'xblock/core': {
+                exports: 'XBlock',
+                deps: ['jquery', 'jquery.immediateDescendents']
+            },
+            'xblock/runtime.v1': {
+                exports: 'XBlock',
+                deps: ['xblock/core']
+            },
+            'mock-ajax': {
+                deps: ['jquery']
+            },
+            'coffee/src/main': {
+                deps: ['coffee/src/ajax_prefix']
+            },
+            'coffee/src/ajax_prefix': {
+                deps: ['jquery']
+            },
+            'modernizr': {
+                exports: 'Modernizr'
+            },
+            'afontgarde': {
+                exports: 'AFontGarde'
+            }
+        }
+    });
+
+    jasmine.getFixtures().fixturesPath += 'coffee/fixtures';
+
+    testFiles = [
+        'cms/js/spec/xblock/cms.runtime.v1_spec',
+        'coffee/spec/main_spec',
+        'coffee/spec/models/course_spec',
+        'coffee/spec/models/metadata_spec',
+        'coffee/spec/models/section_spec',
+        'coffee/spec/models/settings_course_grader_spec',
+        'coffee/spec/models/settings_grading_spec',
+        'coffee/spec/models/textbook_spec',
+        'coffee/spec/models/upload_spec',
+        'coffee/spec/views/course_info_spec',
+        'coffee/spec/views/metadata_edit_spec',
+        'coffee/spec/views/module_edit_spec',
+        'coffee/spec/views/textbook_spec',
+        'coffee/spec/views/upload_spec',
+        'js/spec/video/transcripts/utils_spec',
+        'js/spec/video/transcripts/editor_spec',
+        'js/spec/video/transcripts/videolist_spec',
+        'js/spec/video/transcripts/message_manager_spec',
+        'js/spec/video/transcripts/file_uploader_spec',
+        'js/spec/models/component_template_spec',
+        'js/spec/models/explicit_url_spec',
+        'js/spec/models/xblock_info_spec',
+        'js/spec/models/xblock_validation_spec',
+        'js/spec/models/license_spec',
+        'js/spec/utils/drag_and_drop_spec',
+        'js/spec/utils/handle_iframe_binding_spec',
+        'js/spec/utils/module_spec',
+        'js/spec/views/active_video_upload_list_spec',
+        'js/spec/views/previous_video_upload_spec',
+        'js/spec/views/previous_video_upload_list_spec',
+        'js/spec/views/assets_spec',
+        'js/spec/views/baseview_spec',
+        'js/spec/views/container_spec',
+        'js/spec/views/paged_container_spec',
+        'js/spec/views/group_configuration_spec',
+        'js/spec/views/unit_outline_spec',
+        'js/spec/views/xblock_spec',
+        'js/spec/views/xblock_editor_spec',
+        'js/spec/views/xblock_string_field_editor_spec',
+        'js/spec/views/xblock_validation_spec',
+        'js/spec/views/license_spec',
+        'js/spec/views/paging_spec',
+        'js/spec/views/login_studio_spec',
+        'js/spec/views/pages/container_spec',
+        'js/spec/views/pages/container_subviews_spec',
+        'js/spec/views/pages/group_configurations_spec',
+        'js/spec/views/pages/course_outline_spec',
+        'js/spec/views/pages/course_rerun_spec',
+        'js/spec/views/pages/index_spec',
+        'js/spec/views/pages/library_users_spec',
+        'js/spec/views/modals/base_modal_spec',
+        'js/spec/views/modals/edit_xblock_spec',
+        'js/spec/views/modals/validation_error_modal_spec',
+        'js/spec/views/settings/main_spec',
+        'js/spec/factories/xblock_validation_spec',
+        'js/certificates/spec/models/certificate_spec',
+        'js/certificates/spec/views/certificate_details_spec',
+        'js/certificates/spec/views/certificate_editor_spec',
+        'js/certificates/spec/views/certificates_list_spec',
+        'js/certificates/spec/views/certificate_preview_spec'
+    ];
+
+    i = 0;
+
+    while (i < testFiles.length) {
+        testFiles[i] = '/base/' + testFiles[i] + '.js';
+        i++;
+    }
+
+    specHelpers = [
+        'common/js/spec_helpers/jasmine-extensions',
+        'common/js/spec_helpers/jasmine-stealth',
+        'common/js/spec_helpers/jasmine-waituntil'
+    ];
+
+    requireSerial(specHelpers.concat(testFiles), function() {
+        return window.__karma__.start();
+    });
+
+}).call(this, requirejs, requireSerial);  // jshint ignore:line
diff --git a/cms/static/cms/js/spec/main_squire.js b/cms/static/cms/js/spec/main_squire.js
new file mode 100644
index 0000000000000000000000000000000000000000..8e65cef81ba072b0b62f08aa7a6846cd2b187531
--- /dev/null
+++ b/cms/static/cms/js/spec/main_squire.js
@@ -0,0 +1,218 @@
+(function(requirejs, requireSerial) {
+    'use strict';
+
+    var i, specHelpers, testFiles;
+
+    requirejs.config({
+        baseUrl: '/base/',
+        paths: {
+            'gettext': 'xmodule_js/common_static/js/test/i18n',
+            'mustache': 'xmodule_js/common_static/js/vendor/mustache',
+            'codemirror': 'xmodule_js/common_static/js/vendor/CodeMirror/codemirror',
+            'jquery': 'common/js/vendor/jquery',
+            'jquery-migrate': 'common/js/vendor/jquery-migrate',
+            'jquery.ui': 'xmodule_js/common_static/js/vendor/jquery-ui.min',
+            'jquery.form': 'xmodule_js/common_static/js/vendor/jquery.form',
+            'jquery.markitup': 'xmodule_js/common_static/js/vendor/markitup/jquery.markitup',
+            'jquery.leanModal': 'xmodule_js/common_static/js/vendor/jquery.leanModal',
+            'jquery.smoothScroll': 'xmodule_js/common_static/js/vendor/jquery.smooth-scroll.min',
+            'jquery.scrollTo': 'common/js/vendor/jquery.scrollTo',
+            'jquery.timepicker': 'xmodule_js/common_static/js/vendor/timepicker/jquery.timepicker',
+            'jquery.cookie': 'xmodule_js/common_static/js/vendor/jquery.cookie',
+            'jquery.qtip': 'xmodule_js/common_static/js/vendor/jquery.qtip.min',
+            'jquery.fileupload': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload',
+            'jquery.fileupload-process': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload-process',  // jshint ignore:line
+            'jquery.fileupload-validate': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload-validate',  // jshint ignore:line
+            'jquery.iframe-transport': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.iframe-transport',  // jshint ignore:line
+            'jquery.inputnumber': 'xmodule_js/common_static/js/vendor/html5-input-polyfills/number-polyfill',
+            'jquery.immediateDescendents': 'xmodule_js/common_static/coffee/src/jquery.immediateDescendents',
+            'datepair': 'xmodule_js/common_static/js/vendor/timepicker/datepair',
+            'date': 'xmodule_js/common_static/js/vendor/date',
+            'text': 'xmodule_js/common_static/js/vendor/requirejs/text',
+            'underscore': 'common/js/vendor/underscore',
+            'underscore.string': 'common/js/vendor/underscore.string',
+            'backbone': 'common/js/vendor/backbone',
+            'backbone.associations': 'xmodule_js/common_static/js/vendor/backbone-associations-min',
+            'backbone.paginator': 'common/js/vendor/backbone.paginator',
+            'tinymce': 'xmodule_js/common_static/js/vendor/tinymce/js/tinymce/tinymce.full.min',
+            'jquery.tinymce': 'xmodule_js/common_static/js/vendor/tinymce/js/tinymce/jquery.tinymce',
+            'xmodule': 'xmodule_js/src/xmodule',
+            'xblock/cms.runtime.v1': 'cms/js/xblock/cms.runtime.v1',
+            'xblock': 'common/js/xblock',
+            'utility': 'xmodule_js/common_static/js/src/utility',
+            'sinon': 'xmodule_js/common_static/js/vendor/sinon-1.17.0',
+            'squire': 'xmodule_js/common_static/js/vendor/Squire',
+            'modernizr': 'edx-pattern-library/js/modernizr-custom',
+            'afontgarde': 'edx-pattern-library/js/afontgarde',
+            'edxicons': 'edx-pattern-library/js/edx-icons',
+            'draggabilly': 'xmodule_js/common_static/js/vendor/draggabilly',
+            'domReady': 'xmodule_js/common_static/js/vendor/domReady',
+            'URI': 'xmodule_js/common_static/js/vendor/URI.min',
+            'mathjax': '//cdn.mathjax.org/mathjax/2.6-latest/MathJax.js?config=TeX-MML-AM_SVG&delayStartupUntil=configured',  // jshint ignore:line
+            'youtube': '//www.youtube.com/player_api?noext',
+            'coffee/src/ajax_prefix': 'xmodule_js/common_static/coffee/src/ajax_prefix'
+        },
+        shim: {
+            'gettext': {
+                exports: 'gettext'
+            },
+            'date': {
+                exports: 'Date'
+            },
+            'jquery.ui': {
+                deps: ['jquery'],
+                exports: 'jQuery.ui'
+            },
+            'jquery.form': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.ajaxForm'
+            },
+            'jquery.markitup': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.markitup'
+            },
+            'jquery.leanModal': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.leanModal'
+            },
+            'jquery.smoothScroll': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.smoothScroll'
+            },
+            'jquery.scrollTo': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.scrollTo'
+            },
+            'jquery.cookie': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.cookie'
+            },
+            'jquery.qtip': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.qtip'
+            },
+            'jquery.fileupload': {
+                deps: ['jquery.ui', 'jquery.iframe-transport'],
+                exports: 'jQuery.fn.fileupload'
+            },
+            'jquery.fileupload-process': {
+                deps: ['jquery.fileupload']
+            },
+            'jquery.fileupload-validate': {
+                deps: ['jquery.fileupload']
+            },
+            'jquery.inputnumber': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.inputNumber'
+            },
+            'jquery.tinymce': {
+                deps: ['jquery', 'tinymce'],
+                exports: 'jQuery.fn.tinymce'
+            },
+            'datepair': {
+                deps: ['jquery.ui', 'jquery.timepicker']
+            },
+            'underscore': {
+                exports: '_'
+            },
+            'backbone': {
+                deps: ['underscore', 'jquery'],
+                exports: 'Backbone'
+            },
+            'backbone.associations': {
+                deps: ['backbone'],
+                exports: 'Backbone.Associations'
+            },
+            'backbone.paginator': {
+                deps: ['backbone'],
+                exports: 'Backbone.PageableCollection'
+            },
+            'youtube': {
+                exports: 'YT'
+            },
+            'codemirror': {
+                exports: 'CodeMirror'
+            },
+            'tinymce': {
+                exports: 'tinymce'
+            },
+            'mathjax': {
+                exports: 'MathJax',
+                init: function() {
+                    window.MathJax.Hub.Config({
+                        tex2jax: {
+                            inlineMath: [['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]']],
+                            displayMath: [['\\[', '\\]'], ['[mathjax]', '[/mathjax]']]
+                        }
+                    });
+                    window.MathJax.Hub.Configured();
+                }
+            },
+            'URI': {
+                exports: 'URI'
+            },
+            'xmodule': {
+                exports: 'XModule'
+            },
+            'sinon': {
+                exports: 'sinon'
+            },
+            'common/js/spec_helpers/jasmine-extensions': {
+                deps: ['jquery']
+            },
+            'common/js/spec_helpers/jasmine-stealth': {
+                deps: ['underscore', 'underscore.string']
+            },
+            'common/js/spec_helpers/jasmine-waituntil': {
+                deps: ['jquery']
+            },
+            'xblock/core': {
+                exports: 'XBlock',
+                deps: ['jquery', 'jquery.immediateDescendents']
+            },
+            'xblock/runtime.v1': {
+                exports: 'XBlock',
+                deps: ['xblock/core']
+            },
+            'coffee/src/main': {
+                deps: ['coffee/src/ajax_prefix']
+            },
+            'coffee/src/ajax_prefix': {
+                deps: ['jquery']
+            },
+            'modernizr': {
+                exports: 'Modernizr'
+            },
+            'afontgarde': {
+                exports: 'AFontGarde'
+            }
+        }
+    });
+
+    jasmine.getFixtures().fixturesPath += 'coffee/fixtures';
+
+    testFiles = [
+        'coffee/spec/views/assets_spec',
+        'js/spec/video/translations_editor_spec',
+        'js/spec/video/file_uploader_editor_spec',
+        'js/spec/models/group_configuration_spec'
+    ];
+
+    i = 0;
+
+    while (i < testFiles.length) {
+        testFiles[i] = '/base/' + testFiles[i] + '.js';
+        i++;
+    }
+
+    specHelpers = [
+        'common/js/spec_helpers/jasmine-extensions',
+        'common/js/spec_helpers/jasmine-stealth',
+        'common/js/spec_helpers/jasmine-waituntil'
+    ];
+
+    requireSerial(specHelpers.concat(testFiles), function() {
+        return window.__karma__.start();
+    });
+
+}).call(this, requirejs, requireSerial);  // jshint ignore:line
diff --git a/cms/static/js/spec/xblock/cms.runtime.v1_spec.js b/cms/static/cms/js/spec/xblock/cms.runtime.v1_spec.js
similarity index 79%
rename from cms/static/js/spec/xblock/cms.runtime.v1_spec.js
rename to cms/static/cms/js/spec/xblock/cms.runtime.v1_spec.js
index c508c21548fb81b1872b2c4bfa171958e0645b4f..893fe6827a6d1620cf2974d6a1577357c6025644 100644
--- a/cms/static/js/spec/xblock/cms.runtime.v1_spec.js
+++ b/cms/static/cms/js/spec/xblock/cms.runtime.v1_spec.js
@@ -1,10 +1,11 @@
-define(["js/spec_helpers/edit_helpers", "js/views/modals/base_modal", "xblock/cms.runtime.v1"],
-    function (EditHelpers, BaseModal) {
+define(['js/spec_helpers/edit_helpers', 'js/views/modals/base_modal', 'xblock/cms.runtime.v1'],
+    function(EditHelpers, BaseModal) {
+        'use strict';
 
-        describe("Studio Runtime v1", function() {
+        describe('Studio Runtime v1', function() {
             var runtime;
 
-            beforeEach(function () {
+            beforeEach(function() {
                 EditHelpers.installEditTemplates();
                 runtime = new window.StudioRuntime.v1();
             });
@@ -20,7 +21,7 @@ define(["js/spec_helpers/edit_helpers", "js/views/modals/base_modal", "xblock/cm
             });
 
             it('shows save notifications', function() {
-                var title = "Mock saving...",
+                var title = 'Mock saving...',
                     notificationSpy = EditHelpers.createNotificationSpy();
                 runtime.notify('save', {
                     state: 'start',
@@ -34,9 +35,9 @@ define(["js/spec_helpers/edit_helpers", "js/views/modals/base_modal", "xblock/cm
             });
 
             it('shows error messages', function() {
-                var title = "Mock Error",
-                    message = "This is a mock error.",
-                    notificationSpy = EditHelpers.createNotificationSpy("Error");
+                var title = 'Mock Error',
+                    message = 'This is a mock error.',
+                    notificationSpy = EditHelpers.createNotificationSpy('Error');
                 runtime.notify('error', {
                     title: title,
                     message: message
@@ -44,7 +45,7 @@ define(["js/spec_helpers/edit_helpers", "js/views/modals/base_modal", "xblock/cm
                 EditHelpers.verifyNotificationShowing(notificationSpy, title);
             });
 
-            describe("Modal Dialogs", function() {
+            describe('Modal Dialogs', function() {
                 var MockModal, modal, showMockModal;
 
                 MockModal = BaseModal.extend({
@@ -55,12 +56,12 @@ define(["js/spec_helpers/edit_helpers", "js/views/modals/base_modal", "xblock/cm
 
                 showMockModal = function() {
                     modal = new MockModal({
-                        title: "Mock Modal"
+                        title: 'Mock Modal'
                     });
                     modal.show();
                 };
 
-                beforeEach(function () {
+                beforeEach(function() {
                     EditHelpers.installEditTemplates();
                 });
 
@@ -68,7 +69,7 @@ define(["js/spec_helpers/edit_helpers", "js/views/modals/base_modal", "xblock/cm
                     EditHelpers.hideModalIfShowing(modal);
                 });
 
-                it('cancels a modal dialog', function () {
+                it('cancels a modal dialog', function() {
                     showMockModal();
                     runtime.notify('modal-shown', modal);
                     expect(EditHelpers.isShowingModal(modal)).toBeTruthy();
diff --git a/cms/static/cms/js/xblock/cms.runtime.v1.js b/cms/static/cms/js/xblock/cms.runtime.v1.js
new file mode 100644
index 0000000000000000000000000000000000000000..6c5e73aaf35f43f07c0dbb967c0663fcae882376
--- /dev/null
+++ b/cms/static/cms/js/xblock/cms.runtime.v1.js
@@ -0,0 +1,188 @@
+define(['jquery', 'backbone', 'xblock/runtime.v1', 'URI', 'gettext', 'js/utils/modal',
+        'common/js/components/views/feedback_notification'],
+    function($, Backbone, XBlock, URI, gettext, ModalUtils, NotificationView) {
+        'use strict';
+
+        var __hasProp = {}.hasOwnProperty,
+            __extends = function(child, parent) {
+                var key;
+                for (key in parent) {
+                    if (__hasProp.call(parent, key)) {
+                        child[key] = parent[key];
+                    }
+                }
+                function Ctor() {
+                    this.constructor = child;
+                }
+                Ctor.prototype = parent.prototype;
+                child.prototype = new Ctor();
+                child.__super__ = parent.prototype;
+                return child;
+            },
+            BaseRuntime = {},
+            PreviewRuntime = {},
+            StudioRuntime = {};
+
+        BaseRuntime.v1 = (function(_super) {
+
+            __extends(v1, _super);
+
+            v1.prototype.handlerUrl = function(element, handlerName, suffix, query) {
+                var uri;
+                uri = URI(this.handlerPrefix)
+                    .segment($(element).data('usage-id'))
+                    .segment('handler')
+                    .segment(handlerName);
+                if (suffix !== null) {
+                    uri.segment(suffix);
+                }
+                if (query !== null) {
+                    uri.search(query);
+                }
+                return uri.toString();
+            };
+
+            function v1() {
+                v1.__super__.constructor.call(this);
+                this.dispatcher = _.clone(Backbone.Events);
+                this.listenTo('save', this._handleSave);
+                this.listenTo('cancel', this._handleCancel);
+                this.listenTo('error', this._handleError);
+                this.listenTo('modal-shown', function(data) {
+                    this.modal = data;
+                });
+                this.listenTo('modal-hidden', function() {
+                    this.modal = null;
+                });
+                this.listenTo('page-shown', function(data) {
+                    this.page = data;
+                });
+            }
+
+            /**
+             * Notify the Studio client-side runtime of an event so that it
+             * can update the UI in a consistent way.
+             *
+             * @param {string} name The name of the event.
+             * @param {object} data A JSON representation of the data to be included with the event.
+             */
+            v1.prototype.notify = function(name, data) {
+                this.dispatcher.trigger(name, data);
+            };
+
+            /**
+             * Listen to a Studio event and invoke the specified callback when it is triggered.
+             *
+             * @param {string} name The name of the event.
+             * @param {function} callback The callback to be invoked.
+             */
+            v1.prototype.listenTo = function(name, callback) {
+                this.dispatcher.bind(name, callback, this);
+            };
+
+            /**
+             * Refresh the view for the xblock represented by the specified element.
+             *
+             * @param {element} element The element representing the XBlock.
+             */
+            v1.prototype.refreshXBlock = function(element) {
+                if (this.page) {
+                    this.page.refreshXBlock(element);
+                }
+            };
+
+            v1.prototype._handleError = function(data) {
+                var message, title;
+                message = data.message || data.msg;
+                if (message) {
+                    // TODO: remove 'Open Assessment' specific default title
+                    title = data.title || gettext('OpenAssessment Save Error');
+                    this.alert = new NotificationView.Error({
+                        title: title,
+                        message: message,
+                        closeIcon: false,
+                        shown: false
+                    });
+                    this.alert.show();
+                }
+            };
+
+            v1.prototype._handleSave = function(data) {
+                var message;
+                // Starting to save, so show a notification
+                if (data.state === 'start') {
+                    message = data.message || gettext('Saving');
+                    this.notification = new NotificationView.Mini({
+                        title: message
+                    });
+                    this.notification.show();
+                } else if (data.state === 'end') {
+                    // Finished saving, so hide the notification and refresh appropriately
+                    this._hideAlerts();
+
+                    if (this.modal && this.modal.onSave) {
+                        // Notify the modal that the save has completed so that it can hide itself
+                        // and then refresh the xblock.
+                        this.modal.onSave();
+                    } else if (data.element) {
+                        // ... else ask it to refresh the newly saved xblock
+                        this.refreshXBlock(data.element);
+                    }
+                    this.notification.hide();
+                }
+            };
+
+            v1.prototype._handleCancel = function() {
+                this._hideAlerts();
+                if (this.modal) {
+                    this.modal.cancel();
+                    this.notify('modal-hidden');
+                }
+            };
+
+            /**
+             * Hide any alerts that are being shown.
+             */
+            v1.prototype._hideAlerts = function() {
+                if (this.alert && this.alert.options.shown) {
+                    this.alert.hide();
+                }
+            };
+
+            return v1;
+
+        })(XBlock.Runtime.v1);
+
+        PreviewRuntime.v1 = (function(_super) {
+
+            __extends(v1, _super);
+
+            function v1() {
+                return v1.__super__.constructor.apply(this, arguments);
+            }
+
+            v1.prototype.handlerPrefix = '/preview/xblock';
+
+            return v1;
+
+        })(BaseRuntime.v1);
+
+        StudioRuntime.v1 = (function(_super) {
+
+            __extends(v1, _super);
+
+            function v1() {
+                return v1.__super__.constructor.apply(this, arguments);
+            }
+
+            v1.prototype.handlerPrefix = '/xblock';
+
+            return v1;
+
+        })(BaseRuntime.v1);
+
+        // Install the runtime's into the global namespace
+        window.BaseRuntime = BaseRuntime;
+        window.PreviewRuntime = PreviewRuntime;
+        window.StudioRuntime = StudioRuntime;
+    });
diff --git a/cms/static/coffee/spec/main.coffee b/cms/static/coffee/spec/main.coffee
deleted file mode 100644
index 3685cf47ac187a752172e196518cbbd1d4ecad05..0000000000000000000000000000000000000000
--- a/cms/static/coffee/spec/main.coffee
+++ /dev/null
@@ -1,299 +0,0 @@
-requirejs.config({
-    baseUrl: '/base/',
-    paths: {
-        "gettext": "xmodule_js/common_static/js/test/i18n",
-        "mustache": "xmodule_js/common_static/js/vendor/mustache",
-        "codemirror": "xmodule_js/common_static/js/vendor/CodeMirror/codemirror",
-        "jquery": "xmodule_js/common_static/common/js/vendor/jquery",
-        "jquery-migrate": "xmodule_js/common_static/common/js/vendor/jquery-migrate",
-        "jquery.ui": "xmodule_js/common_static/js/vendor/jquery-ui.min",
-        "jquery.form": "xmodule_js/common_static/js/vendor/jquery.form",
-        "jquery.markitup": "xmodule_js/common_static/js/vendor/markitup/jquery.markitup",
-        "jquery.leanModal": "xmodule_js/common_static/js/vendor/jquery.leanModal",
-        "jquery.ajaxQueue": "xmodule_js/common_static/js/vendor/jquery.ajaxQueue",
-        "jquery.smoothScroll": "xmodule_js/common_static/js/vendor/jquery.smooth-scroll.min",
-        "jquery.scrollTo": "common/js/vendor/jquery.scrollTo",
-        "jquery.timepicker": "xmodule_js/common_static/js/vendor/timepicker/jquery.timepicker",
-        "jquery.cookie": "xmodule_js/common_static/js/vendor/jquery.cookie",
-        "jquery.qtip": "xmodule_js/common_static/js/vendor/jquery.qtip.min",
-        "jquery.fileupload": "xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload",
-        "jquery.fileupload-process": "xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload-process",
-        "jquery.fileupload-validate": "xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload-validate",
-        "jquery.iframe-transport": "xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.iframe-transport",
-        "jquery.inputnumber": "xmodule_js/common_static/js/vendor/html5-input-polyfills/number-polyfill",
-        "jquery.immediateDescendents": "xmodule_js/common_static/coffee/src/jquery.immediateDescendents",
-        "jquery.simulate": "xmodule_js/common_static/js/vendor/jquery.simulate",
-        "datepair": "xmodule_js/common_static/js/vendor/timepicker/datepair",
-        "date": "xmodule_js/common_static/js/vendor/date",
-        "moment": "xmodule_js/common_static/js/vendor/moment.min",
-        "moment-with-locales": "xmodule_js/common_static/js/vendor/moment-with-locales.min",
-        "text": "xmodule_js/common_static/js/vendor/requirejs/text",
-        "underscore": "common/js/vendor/underscore",
-        "underscore.string": "common/js/vendor/underscore.string",
-        "backbone": "common/js/vendor/backbone",
-        "backbone.associations": "xmodule_js/common_static/js/vendor/backbone-associations-min",
-        "backbone.paginator": "common/js/vendor/backbone.paginator",
-        "backbone-relational": "xmodule_js/common_static/js/vendor/backbone-relational.min",
-        "tinymce": "xmodule_js/common_static/js/vendor/tinymce/js/tinymce/tinymce.full.min",
-        "jquery.tinymce": "xmodule_js/common_static/js/vendor/tinymce/js/tinymce/jquery.tinymce",
-        "xmodule": "xmodule_js/src/xmodule",
-        "xblock/cms.runtime.v1": "coffee/src/xblock/cms.runtime.v1",
-        "xblock/core": "xmodule_js/common_static/js/xblock/core",
-        "xblock": "xmodule_js/common_static/coffee/src/xblock",
-        "utility": "xmodule_js/common_static/js/src/utility",
-        "accessibility": "xmodule_js/common_static/js/src/accessibility_tools",
-        "sinon": "xmodule_js/common_static/js/vendor/sinon-1.17.0",
-        "squire": "xmodule_js/common_static/js/vendor/Squire",
-        "jasmine-imagediff": "xmodule_js/common_static/js/vendor/jasmine-imagediff",
-        "draggabilly": "xmodule_js/common_static/js/vendor/draggabilly",
-        "domReady": "xmodule_js/common_static/js/vendor/domReady",
-        "URI": "xmodule_js/common_static/js/vendor/URI.min",
-        "mock-ajax": "xmodule_js/common_static/js/vendor/mock-ajax",
-        "modernizr": "edx-pattern-library/js/modernizr-custom",
-        "afontgarde": "edx-pattern-library/js/afontgarde",
-        "edxicons": "edx-pattern-library/js/edx-icons",
-
-        "mathjax": "//cdn.mathjax.org/mathjax/2.6-latest/MathJax.js?config=TeX-MML-AM_SVG&delayStartupUntil=configured",
-        "youtube": "//www.youtube.com/player_api?noext",
-
-        "coffee/src/ajax_prefix": "xmodule_js/common_static/coffee/src/ajax_prefix",
-        "js/spec/test_utils": "js/spec/test_utils",
-    }
-    shim: {
-        "gettext": {
-            exports: "gettext"
-        },
-        "date": {
-            exports: "Date"
-        },
-        "jquery-migrate": ['jquery'],
-        "jquery.ui": {
-            deps: ["jquery"],
-            exports: "jQuery.ui"
-        },
-        "jquery.form": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.ajaxForm"
-        },
-        "jquery.markitup": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.markitup"
-        },
-        "jquery.leanModal": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.leanModal"
-        },
-        "jquery.smoothScroll": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.smoothScroll"
-        },
-        "jquery.ajaxQueue": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.ajaxQueue"
-        },
-        "jquery.scrollTo": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.scrollTo"
-        },
-        "jquery.cookie": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.cookie"
-        },
-        "jquery.qtip": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.qtip"
-        },
-        "jquery.fileupload": {
-            deps: ["jquery.ui", "jquery.iframe-transport"],
-            exports: "jQuery.fn.fileupload"
-        },
-        "jquery.fileupload-process": {
-            deps: ["jquery.fileupload"]
-        },
-        "jquery.fileupload-validate": {
-            deps: ["jquery.fileupload"]
-        },
-        "jquery.inputnumber": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.inputNumber"
-        },
-        "jquery.simulate": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.simulate"
-        },
-        "jquery.tinymce": {
-            deps: ["jquery", "tinymce"],
-            exports: "jQuery.fn.tinymce"
-        },
-        "datepair": {
-            deps: ["jquery.ui", "jquery.timepicker"]
-        },
-        "underscore": {
-            exports: "_"
-        },
-        "backbone": {
-            deps: ["underscore", "jquery"],
-            exports: "Backbone"
-        },
-        "backbone.associations": {
-            deps: ["backbone"],
-            exports: "Backbone.Associations"
-        },
-        "backbone.paginator": {
-            deps: ["backbone"],
-            exports: "Backbone.PageableCollection"
-        },
-        "backbone-relational": {
-            deps: ["backbone"],
-        },
-        "youtube": {
-            exports: "YT"
-        },
-        "codemirror": {
-            exports: "CodeMirror"
-        },
-        "tinymce": {
-            exports: "tinymce"
-        },
-        "mathjax": {
-            exports: "MathJax",
-            init: ->
-                MathJax.Hub.Config
-                    tex2jax:
-                        inlineMath: [
-                            ["\\(", "\\)"],
-                            ['[mathjaxinline]', '[/mathjaxinline]']
-                        ]
-                        displayMath: [
-                            ["\\[", "\\]"],
-                            ['[mathjax]', '[/mathjax]']
-                        ]
-                MathJax.Hub.Configured()
-        },
-        "URI": {
-            exports: "URI"
-        },
-        "xmodule": {
-            exports: "XModule"
-        },
-        "sinon": {
-            exports: "sinon"
-        },
-        "jasmine-imagediff": {},
-        "common/js/spec_helpers/jasmine-extensions": {
-            deps: ["jquery"]
-        },
-        "common/js/spec_helpers/jasmine-stealth": {
-            deps: ["underscore", "underscore.string"]
-        },
-        "common/js/spec_helpers/jasmine-waituntil": {
-            deps: ["jquery"]
-        },
-        "xblock/core": {
-            exports: "XBlock",
-            deps: ["jquery", "jquery.immediateDescendents"]
-        },
-        "xblock/runtime.v1": {
-            exports: "XBlock",
-            deps: ["xblock/core"]
-        },
-        "mock-ajax": {
-            deps: ["jquery"]
-        }
-
-        "coffee/src/main": {
-            deps: ["coffee/src/ajax_prefix"]
-        },
-        "coffee/src/ajax_prefix": {
-            deps: ["jquery"]
-        },
-        "modernizr": {
-            exports: "Modernizr"
-        },
-        "afontgarde": {
-            exports: "AFontGarde"
-        }
-    }
-});
-
-jasmine.getFixtures().fixturesPath += 'coffee/fixtures'
-
-testFiles = [
-    "coffee/spec/main_spec",
-    "coffee/spec/models/course_spec",
-    "coffee/spec/models/metadata_spec",
-    "coffee/spec/models/section_spec",
-    "coffee/spec/models/settings_course_grader_spec",
-    "coffee/spec/models/settings_grading_spec",
-    "coffee/spec/models/textbook_spec",
-    "coffee/spec/models/upload_spec",
-    "coffee/spec/views/course_info_spec",
-    "coffee/spec/views/metadata_edit_spec",
-    "coffee/spec/views/module_edit_spec",
-    "coffee/spec/views/textbook_spec",
-    "coffee/spec/views/upload_spec",
-    "js/spec/video/transcripts/utils_spec",
-    "js/spec/video/transcripts/editor_spec",
-    "js/spec/video/transcripts/videolist_spec",
-    "js/spec/video/transcripts/message_manager_spec",
-    "js/spec/video/transcripts/file_uploader_spec",
-    "js/spec/models/component_template_spec",
-    "js/spec/models/explicit_url_spec",
-    "js/spec/models/xblock_info_spec",
-    "js/spec/models/xblock_validation_spec",
-    "js/spec/models/license_spec",
-    "js/spec/utils/drag_and_drop_spec",
-    "js/spec/utils/handle_iframe_binding_spec",
-    "js/spec/utils/module_spec",
-    "js/spec/views/active_video_upload_list_spec",
-    "js/spec/views/previous_video_upload_spec",
-    "js/spec/views/previous_video_upload_list_spec",
-    "js/spec/views/assets_spec",
-    "js/spec/views/baseview_spec",
-    "js/spec/views/container_spec",
-    "js/spec/views/paged_container_spec",
-    "js/spec/views/group_configuration_spec",
-    "js/spec/views/unit_outline_spec",
-    "js/spec/views/xblock_spec",
-    "js/spec/views/xblock_editor_spec",
-    "js/spec/views/xblock_string_field_editor_spec",
-    "js/spec/views/xblock_validation_spec",
-    "js/spec/views/license_spec",
-    "js/spec/views/paging_spec",
-    "js/spec/views/login_studio_spec",
-    "js/spec/views/pages/container_spec",
-    "js/spec/views/pages/container_subviews_spec",
-    "js/spec/views/pages/group_configurations_spec",
-    "js/spec/views/pages/course_outline_spec",
-    "js/spec/views/pages/course_rerun_spec",
-    "js/spec/views/pages/index_spec",
-    "js/spec/views/pages/library_users_spec",
-    "js/spec/views/modals/base_modal_spec",
-    "js/spec/views/modals/edit_xblock_spec",
-    "js/spec/views/modals/validation_error_modal_spec",
-    "js/spec/views/settings/main_spec",
-    "js/spec/factories/xblock_validation_spec",
-    "js/spec/xblock/cms.runtime.v1_spec",
-    "js/certificates/spec/models/certificate_spec",
-    "js/certificates/spec/views/certificate_details_spec",
-    "js/certificates/spec/views/certificate_editor_spec",
-    "js/certificates/spec/views/certificates_list_spec",
-    "js/certificates/spec/views/certificate_preview_spec"
-]
-
-i = 0
-while i < testFiles.length
-    testFiles[i] = '/base/' + testFiles[i] + '.js'
-    i++
-
-specHelpers = [
-  'common/js/spec_helpers/jasmine-extensions',
-  'common/js/spec_helpers/jasmine-stealth',
-  'common/js/spec_helpers/jasmine-waituntil'
-]
-
-# Jasmine has a global stack for creating a tree of specs. We need to load
-# spec files one by one, otherwise some end up getting nested under others.
-requireSerial specHelpers.concat(testFiles), ->
-# start test run, once Require.js is done
-    window.__karma__.start()
diff --git a/cms/static/coffee/spec/main_squire.coffee b/cms/static/coffee/spec/main_squire.coffee
deleted file mode 100644
index 3743e9602dbc7b8503354fe9f8c620f6345bce33..0000000000000000000000000000000000000000
--- a/cms/static/coffee/spec/main_squire.coffee
+++ /dev/null
@@ -1,218 +0,0 @@
-requirejs.config({
-    baseUrl: '/base/',
-
-    paths: {
-        "gettext": "xmodule_js/common_static/js/test/i18n",
-        "mustache": "xmodule_js/common_static/js/vendor/mustache",
-        "codemirror": "xmodule_js/common_static/js/vendor/CodeMirror/codemirror",
-        "jquery": "common/js/vendor/jquery",
-        "jquery-migrate": "common/js/vendor/jquery-migrate",
-        "jquery.ui": "xmodule_js/common_static/js/vendor/jquery-ui.min",
-        "jquery.form": "xmodule_js/common_static/js/vendor/jquery.form",
-        "jquery.markitup": "xmodule_js/common_static/js/vendor/markitup/jquery.markitup",
-        "jquery.leanModal": "xmodule_js/common_static/js/vendor/jquery.leanModal",
-        "jquery.smoothScroll": "xmodule_js/common_static/js/vendor/jquery.smooth-scroll.min",
-        "jquery.scrollTo": "common/js/vendor/jquery.scrollTo",
-        "jquery.timepicker": "xmodule_js/common_static/js/vendor/timepicker/jquery.timepicker",
-        "jquery.cookie": "xmodule_js/common_static/js/vendor/jquery.cookie",
-        "jquery.qtip": "xmodule_js/common_static/js/vendor/jquery.qtip.min",
-        "jquery.fileupload": "xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload",
-        "jquery.fileupload-process": "xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload-process",
-        "jquery.fileupload-validate": "xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload-validate",
-        "jquery.iframe-transport": "xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.iframe-transport",
-        "jquery.inputnumber": "xmodule_js/common_static/js/vendor/html5-input-polyfills/number-polyfill",
-        "jquery.immediateDescendents": "xmodule_js/common_static/coffee/src/jquery.immediateDescendents",
-        "datepair": "xmodule_js/common_static/js/vendor/timepicker/datepair",
-        "date": "xmodule_js/common_static/js/vendor/date",
-        "text": "xmodule_js/common_static/js/vendor/requirejs/text",
-        "underscore": "common/js/vendor/underscore",
-        "underscore.string": "common/js/vendor/underscore.string",
-        "backbone": "common/js/vendor/backbone",
-        "backbone.associations": "xmodule_js/common_static/js/vendor/backbone-associations-min",
-        "backbone.paginator": "common/js/vendor/backbone.paginator",
-        "tinymce": "xmodule_js/common_static/js/vendor/tinymce/js/tinymce/tinymce.full.min",
-        "jquery.tinymce": "xmodule_js/common_static/js/vendor/tinymce/js/tinymce/jquery.tinymce",
-        "xmodule": "xmodule_js/src/xmodule",
-        "xblock/cms.runtime.v1": "coffee/src/xblock/cms.runtime.v1",
-        "xblock/core": "xmodule_js/common_static/js/xblock/core",
-        "xblock": "xmodule_js/common_static/coffee/src/xblock",
-        "utility": "xmodule_js/common_static/js/src/utility",
-        "sinon": "xmodule_js/common_static/js/vendor/sinon-1.17.0",
-        "squire": "xmodule_js/common_static/js/vendor/Squire",
-        "modernizr": "edx-pattern-library/js/modernizr-custom",
-        "afontgarde": "edx-pattern-library/js/afontgarde",
-        "edxicons": "edx-pattern-library/js/edx-icons",
-        "draggabilly": "xmodule_js/common_static/js/vendor/draggabilly",
-        "domReady": "xmodule_js/common_static/js/vendor/domReady",
-        "URI": "xmodule_js/common_static/js/vendor/URI.min",
-
-        "mathjax": "//cdn.mathjax.org/mathjax/2.6-latest/MathJax.js?config=TeX-MML-AM_SVG&delayStartupUntil=configured",
-        "youtube": "//www.youtube.com/player_api?noext",
-
-        "coffee/src/ajax_prefix": "xmodule_js/common_static/coffee/src/ajax_prefix"
-    }
-    shim: {
-        "gettext": {
-            exports: "gettext"
-        },
-        "date": {
-            exports: "Date"
-        },
-        "jquery.ui": {
-            deps: ["jquery"],
-            exports: "jQuery.ui"
-        },
-        "jquery.form": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.ajaxForm"
-        },
-        "jquery.markitup": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.markitup"
-        },
-        "jquery.leanModal": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.leanModal"
-        },
-        "jquery.smoothScroll": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.smoothScroll"
-        },
-        "jquery.scrollTo": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.scrollTo"
-        },
-        "jquery.cookie": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.cookie"
-        },
-        "jquery.qtip": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.qtip"
-        },
-        "jquery.fileupload": {
-            deps: ["jquery.ui", "jquery.iframe-transport"],
-            exports: "jQuery.fn.fileupload"
-        },
-        "jquery.fileupload-process": {
-            deps: ["jquery.fileupload"]
-        },
-        "jquery.fileupload-validate": {
-            deps: ["jquery.fileupload"]
-        },
-        "jquery.inputnumber": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.inputNumber"
-        },
-        "jquery.tinymce": {
-            deps: ["jquery", "tinymce"],
-            exports: "jQuery.fn.tinymce"
-        },
-        "datepair": {
-            deps: ["jquery.ui", "jquery.timepicker"]
-        },
-        "underscore": {
-            exports: "_"
-        },
-        "backbone": {
-            deps: ["underscore", "jquery"],
-            exports: "Backbone"
-        },
-        "backbone.associations": {
-            deps: ["backbone"],
-            exports: "Backbone.Associations"
-        },
-        "backbone.paginator": {
-            deps: ["backbone"],
-            exports: "Backbone.PageableCollection"
-        },
-        "youtube": {
-            exports: "YT"
-        },
-        "codemirror": {
-            exports: "CodeMirror"
-        },
-        "tinymce": {
-            exports: "tinymce"
-        },
-        "mathjax": {
-            exports: "MathJax",
-            init: ->
-              MathJax.Hub.Config
-                tex2jax:
-                  inlineMath: [
-                    ["\\(","\\)"],
-                    ['[mathjaxinline]','[/mathjaxinline]']
-                  ]
-                  displayMath: [
-                    ["\\[","\\]"],
-                    ['[mathjax]','[/mathjax]']
-                  ]
-              MathJax.Hub.Configured();
-        },
-        "URI": {
-            exports: "URI"
-        },
-        "xmodule": {
-            exports: "XModule"
-        },
-        "sinon": {
-            exports: "sinon"
-        },
-        "common/js/spec_helpers/jasmine-extensions": {
-            deps: ["jquery"]
-        },
-        "common/js/spec_helpers/jasmine-stealth": {
-            deps: ["underscore", "underscore.string"]
-        },
-        "common/js/spec_helpers/jasmine-waituntil": {
-            deps: ["jquery"]
-        },
-        "xblock/core": {
-            exports: "XBlock",
-            deps: ["jquery", "jquery.immediateDescendents"]
-        },
-        "xblock/runtime.v1": {
-            exports: "XBlock",
-            deps: ["xblock/core"]
-        },
-
-        "coffee/src/main": {
-            deps: ["coffee/src/ajax_prefix"]
-        },
-        "coffee/src/ajax_prefix": {
-            deps: ["jquery"]
-        },
-        "modernizr": {
-            exports: "Modernizr"
-        },
-        "afontgarde": {
-            exports: "AFontGarde"
-        }
-    }
-});
-
-jasmine.getFixtures().fixturesPath += 'coffee/fixtures'
-
-testFiles = [
-    'coffee/spec/views/assets_spec',
-    'js/spec/video/translations_editor_spec',
-    'js/spec/video/file_uploader_editor_spec',
-    'js/spec/models/group_configuration_spec'
-]
-i = 0
-while i < testFiles.length
-    testFiles[i] = '/base/' + testFiles[i] + '.js'
-    i++
-
-specHelpers = [
-  'common/js/spec_helpers/jasmine-extensions',
-  'common/js/spec_helpers/jasmine-stealth',
-  'common/js/spec_helpers/jasmine-waituntil'
-]
-
-# Jasmine has a global stack for creating a tree of specs. We need to load
-# spec files one by one, otherwise some end up getting nested under others.
-requireSerial specHelpers.concat(testFiles), ->
-# start test run, once Require.js is done
-    window.__karma__.start()
diff --git a/cms/static/coffee/src/xblock/cms.runtime.v1.coffee b/cms/static/coffee/src/xblock/cms.runtime.v1.coffee
deleted file mode 100644
index 4cbade87bfa60536fd3e0f3647aa5dc27a259a1d..0000000000000000000000000000000000000000
--- a/cms/static/coffee/src/xblock/cms.runtime.v1.coffee
+++ /dev/null
@@ -1,96 +0,0 @@
-define [
-    "jquery", "backbone", "xblock/runtime.v1", "URI", "gettext",
-    "js/utils/modal", "common/js/components/views/feedback_notification"
-], ($, Backbone, XBlock, URI, gettext, ModalUtils, NotificationView) ->
-
-    @BaseRuntime = {}
-
-    class BaseRuntime.v1 extends XBlock.Runtime.v1
-        handlerUrl: (element, handlerName, suffix, query, thirdparty) ->
-            uri = URI(@handlerPrefix).segment($(element).data('usage-id'))
-            .segment('handler')
-            .segment(handlerName)
-            if suffix? then uri.segment(suffix)
-            if query? then uri.search(query)
-            uri.toString()
-
-        constructor: () ->
-            super()
-            @dispatcher = _.clone(Backbone.Events)
-            @listenTo('save', @_handleSave)
-            @listenTo('cancel', @_handleCancel)
-            @listenTo('error', @_handleError)
-            @listenTo('modal-shown', (data) ->
-                @modal = data)
-            @listenTo('modal-hidden', () ->
-                @modal = null)
-            @listenTo('page-shown', (data) ->
-                @page = data)
-
-        # Notify the Studio client-side runtime of an event so that it can update the UI in a consistent way.
-        notify: (name, data) ->
-            @dispatcher.trigger(name, data)
-
-        # Listen to a Studio event and invoke the specified callback when it is triggered.
-        listenTo: (name, callback) ->
-            @dispatcher.bind(name, callback, this)
-
-        # Refresh the view for the xblock represented by the specified element.
-        refreshXBlock: (element) ->
-            if @page
-                @page.refreshXBlock(element)
-
-        _handleError: (data) ->
-            message = data.message || data.msg
-            if message
-                # TODO: remove 'Open Assessment' specific default title
-                title = data.title || gettext("OpenAssessment Save Error")
-                @alert = new NotificationView.Error
-                    title: title
-                    message: message
-                    closeIcon: false
-                    shown: false
-                @alert.show()
-
-        _handleSave: (data) ->
-            # Starting to save, so show a notification
-            if data.state == 'start'
-                message = data.message || gettext('Saving')
-                @notification = new NotificationView.Mini
-                    title: message
-                @notification.show()
-
-            # Finished saving, so hide the notification and refresh appropriately
-            else if data.state == 'end'
-                @_hideAlerts()
-
-                # Notify the modal that the save has completed so that it can hide itself
-                # and then refresh the xblock.
-                if @modal and @modal.onSave
-                    @modal.onSave()
-                # ... else ask it to refresh the newly saved xblock
-                else if data.element
-                    @refreshXBlock(data.element)
-
-                @notification.hide()
-
-        _handleCancel: () ->
-            @_hideAlerts()
-            if @modal
-                @modal.cancel()
-                @notify('modal-hidden')
-
-        _hideAlerts: () ->
-            # Hide any alerts that are being shown
-            if @alert && @alert.options.shown
-                @alert.hide()
-
-    @PreviewRuntime = {}
-
-    class PreviewRuntime.v1 extends BaseRuntime.v1
-        handlerPrefix: '/preview/xblock'
-
-    @StudioRuntime = {}
-
-    class StudioRuntime.v1 extends BaseRuntime.v1
-        handlerPrefix: '/xblock'
diff --git a/cms/static/karma_cms.conf.js b/cms/static/karma_cms.conf.js
index edfb65d9f49bad318d3cef8101ee564b97a11ac2..f2ef478cd36b8c39d449be6c9e66fc1bca8ed598 100644
--- a/cms/static/karma_cms.conf.js
+++ b/cms/static/karma_cms.conf.js
@@ -21,11 +21,13 @@ var options = {
     // Make sure the patterns in sourceFiles and specFiles do not match the same file.
     // Otherwise Istanbul which is used for coverage tracking will cause tests to not run.
     sourceFiles: [
+        {pattern: 'cms/**/!(*spec|djangojs).js'},
         {pattern: 'coffee/src/**/!(*spec).js'},
         {pattern: 'js/**/!(*spec|djangojs).js'}
     ],
 
     specFiles: [
+        {pattern: 'cms/**/*spec.js'},
         {pattern: 'coffee/spec/**/*spec.js'},
         {pattern: 'js/certificates/spec/**/*spec.js'},
         {pattern: 'js/spec/**/*spec.js'}
@@ -37,10 +39,10 @@ var options = {
     ],
 
     runFiles: [
-        {pattern: 'coffee/spec/main.js', included: true}
+        {pattern: 'cms/js/spec/main.js', included: true}
     ]
 };
 
-module.exports = function (config) {
+module.exports = function(config) {
     configModule.configure(config, options);
 };
diff --git a/cms/static/karma_cms_squire.conf.js b/cms/static/karma_cms_squire.conf.js
index 093e51bb554137ef63f5178e5474236d127421e8..d8bdda0c989446934b01330e470fe1a27fd36b00 100644
--- a/cms/static/karma_cms_squire.conf.js
+++ b/cms/static/karma_cms_squire.conf.js
@@ -36,7 +36,7 @@ var options = {
     ],
 
     runFiles: [
-        {pattern: 'coffee/spec/main_squire.js', included: true}
+        {pattern: 'cms/js/spec/main_squire.js', included: true}
     ]
 };
 
diff --git a/common/static/coffee/spec/xblock/core_spec.coffee b/common/static/coffee/spec/xblock/core_spec.coffee
deleted file mode 100644
index e04184710a9c5b4a06e11263ee575adadb95c3d8..0000000000000000000000000000000000000000
--- a/common/static/coffee/spec/xblock/core_spec.coffee
+++ /dev/null
@@ -1,96 +0,0 @@
-describe "XBlock", ->
-  beforeEach ->
-    setFixtures """
-      <div>
-        <div class='xblock'
-             id='vA'
-             data-runtime-version="A"
-             data-runtime-class="TestRuntime"
-             data-init="initFnA"
-             data-name="a-name"
-        />
-        <div>
-          <div class='xblock'
-               id='vZ'
-               data-runtime-version="Z"
-               data-runtime-class="TestRuntime"
-               data-init="initFnZ"
-               data-request-token="req-token-z"
-          />
-        </div>
-        <div class='xblock' id='missing-version' data-init='initFnA' data-name='no-version'/>
-        <div class='xblock' id='missing-init' data-runtime-version="A" data-name='no-init'/>
-      </div>
-      """
-
-  describe "initializeBlock", ->
-    beforeEach ->
-      window.TestRuntime = {}
-      @runtimeA = {name: 'runtimeA'}
-      @runtimeZ = {name: 'runtimeZ'}
-      TestRuntime.vA = jasmine.createSpy().and.returnValue(@runtimeA)
-      TestRuntime.vZ = jasmine.createSpy().and.returnValue(@runtimeZ)
-
-      window.initFnA = jasmine.createSpy()
-      window.initFnZ = jasmine.createSpy()
-
-      @fakeChildren = ['list', 'of', 'children']
-      spyOn(XBlock, 'initializeXBlocks').and.returnValue(@fakeChildren)
-
-      @vANode = $('#vA')[0]
-      @vZNode = $('#vZ')[0]
-
-      @vABlock = XBlock.initializeBlock(@vANode, 'req-token-a')
-      @vZBlock = XBlock.initializeBlock(@vZNode)
-      @missingVersionBlock = XBlock.initializeBlock($('#missing-version')[0])
-      @missingInitBlock = XBlock.initializeBlock($('#missing-init')[0])
-
-    it "loads the right runtime version", ->
-      expect(TestRuntime.vA).toHaveBeenCalledWith()
-      expect(TestRuntime.vZ).toHaveBeenCalledWith()
-
-    it "loads the right init function", ->
-      expect(window.initFnA).toHaveBeenCalledWith(@runtimeA, @vANode, {})
-      expect(window.initFnZ).toHaveBeenCalledWith(@runtimeZ, @vZNode, {})
-
-    it "loads when missing versions", ->
-      expect(@missingVersionBlock.element).toBe($('#missing-version')[0])
-      expect(@missingVersionBlock.name).toBe('no-version')
-
-    it "loads when missing init fn", ->
-      expect(@missingInitBlock.element).toBe($('#missing-init')[0])
-      expect(@missingInitBlock.name).toBe('no-init')
-
-    it "adds names to blocks", ->
-      expect(@vABlock.name).toBe('a-name')
-
-    it "leaves leaves missing names undefined", ->
-      expect(@vZBlock.name).toBeUndefined()
-
-    it "attaches the element to the block", ->
-      expect(@vABlock.element).toBe(@vANode)
-      expect(@vZBlock.element).toBe(@vZNode)
-      expect(@missingVersionBlock.element).toBe($('#missing-version')[0])
-      expect(@missingInitBlock.element).toBe($('#missing-init')[0])
-
-    it "passes through the request token", ->
-      expect(XBlock.initializeXBlocks).toHaveBeenCalledWith($(@vANode), 'req-token-a')
-      expect(XBlock.initializeXBlocks).toHaveBeenCalledWith($(@vZNode), 'req-token-z')
-
-
-  describe "initializeBlocks", ->
-    beforeEach ->
-      spyOn(XBlock, 'initializeBlock')
-
-      @vANode = $('#vA')[0]
-      @vZNode = $('#vZ')[0]
-
-    it "initializes children", ->
-      XBlock.initializeBlocks($('#jasmine-fixtures'))
-      expect(XBlock.initializeBlock).toHaveBeenCalledWith(@vANode, undefined)
-      expect(XBlock.initializeBlock).toHaveBeenCalledWith(@vZNode, undefined)
-
-    it "only initializes matching request tokens", ->
-      XBlock.initializeBlocks($('#jasmine-fixtures'), 'req-token-z')
-      expect(XBlock.initializeBlock).not.toHaveBeenCalledWith(@vANode, jasmine.any(Object))
-      expect(XBlock.initializeBlock).toHaveBeenCalledWith(@vZNode, 'req-token-z')
diff --git a/common/static/coffee/spec/xblock/runtime.v1_spec.coffee b/common/static/coffee/spec/xblock/runtime.v1_spec.coffee
deleted file mode 100644
index 7bad77494d631d6587da1105cf89e7d047fe018b..0000000000000000000000000000000000000000
--- a/common/static/coffee/spec/xblock/runtime.v1_spec.coffee
+++ /dev/null
@@ -1,21 +0,0 @@
-describe "XBlock.Runtime.v1", ->
-  beforeEach ->
-    setFixtures """
-      <div class='xblock' data-handler-prefix='/xblock/fake-usage-id/handler'/>
-    """
-    @children = [
-      {name: 'childA'},
-      {name: 'childB'}
-    ]
-
-    @element = $('.xblock')[0]
-    $(@element).prop('xblock_children', @children)
-
-    @runtime = new XBlock.Runtime.v1(@element)
-
-  it "provides a list of children", ->
-    expect(@runtime.children(@element)).toBe(@children)
-
-  it "maps children by name", ->
-    expect(@runtime.childMap(@element, 'childA')).toBe(@children[0])
-    expect(@runtime.childMap(@element, 'childB')).toBe(@children[1])
diff --git a/common/static/coffee/src/xblock/runtime.v1.coffee b/common/static/coffee/src/xblock/runtime.v1.coffee
deleted file mode 100644
index fbe70dc878602d0da1b370db2f61524366f6aba7..0000000000000000000000000000000000000000
--- a/common/static/coffee/src/xblock/runtime.v1.coffee
+++ /dev/null
@@ -1,14 +0,0 @@
-class XBlock.Runtime.v1
-  children: (block) => $(block).prop('xblock_children')
-  childMap: (block, childName) =>
-    for child in @children(block)
-        return child if child.name == childName
-
-  # Notify the client-side runtime that an event has occurred.
-  # This allows the runtime to update the UI in a consistent way
-  # for different XBlocks.
-  # `name` is an arbitrary string (for example, "save")
-  # `data` is an object (for example, {state: 'starting'})
-  # The default implementation is a no-op.
-  # WARNING: This is an interim solution and not officially supported!
-  notify: (name, data) -> undefined
diff --git a/common/static/common/js/karma.common.conf.js b/common/static/common/js/karma.common.conf.js
index 36be0d44a50a69c8fdda68f1b3d7bfe35ea592b2..f816b3ce7cc7600134cdda85c18185f53fd106f3 100644
--- a/common/static/common/js/karma.common.conf.js
+++ b/common/static/common/js/karma.common.conf.js
@@ -54,8 +54,7 @@ var commonFiles = {
     ],
 
     sourceFiles: [
-        {pattern: 'common/js/components/**/*.js'},
-        {pattern: 'common/js/utils/**/*.js'}
+        {pattern: 'common/js/!(spec_helpers)/**/!(*spec).js'}
     ],
 
     specFiles: [
diff --git a/common/static/common/js/spec/xblock/core_spec.js b/common/static/common/js/spec/xblock/core_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..9e2a33c363650605fb0feb7f3ab4130cbc8b2fa4
--- /dev/null
+++ b/common/static/common/js/spec/xblock/core_spec.js
@@ -0,0 +1,111 @@
+(function() {
+    'use strict';
+
+    describe('XBlock', function() {
+        beforeEach(function() {
+            return setFixtures(
+                '<div>\n' +
+                '  <div class="xblock"\n' +
+                '       id="vA"\n' +
+                '       data-runtime-version="A"\n' +
+                '       data-runtime-class="TestRuntime"\n' +
+                '       data-init="initFnA"\n' +
+                '       data-name="a-name"\n' +
+                '  />\n' +
+                '  <div>\n' +
+                '    <div class="xblock"\n' +
+                '         id="vZ"\n' +
+                '         data-runtime-version="Z"\n' +
+                '         data-runtime-class="TestRuntime"\n' +
+                '         data-init="initFnZ"\n' +
+                '         data-request-token="req-token-z"\n' +
+                '    />\n' +
+                '  </div>\n' +
+                '  <div class="xblock" id="missing-version" data-init="initFnA" data-name="no-version"/>\n' +
+                '  <div class="xblock" id="missing-init" data-runtime-version="A" data-name="no-init"/>\n' +
+                '</div>');
+        });
+        describe('initializeBlock', function() {
+            beforeEach(function() {
+                window.TestRuntime = {};
+                this.runtimeA = {
+                    name: 'runtimeA'
+                };
+                this.runtimeZ = {
+                    name: 'runtimeZ'
+                };
+                window.TestRuntime.vA = jasmine.createSpy().and.returnValue(this.runtimeA);
+                window.TestRuntime.vZ = jasmine.createSpy().and.returnValue(this.runtimeZ);
+                window.initFnA = jasmine.createSpy();
+                window.initFnZ = jasmine.createSpy();
+                this.fakeChildren = ['list', 'of', 'children'];
+                spyOn(XBlock, 'initializeXBlocks').and.returnValue(this.fakeChildren);
+                this.vANode = $('#vA')[0];
+                this.vZNode = $('#vZ')[0];
+                this.vABlock = XBlock.initializeBlock(this.vANode, 'req-token-a');
+                this.vZBlock = XBlock.initializeBlock(this.vZNode);
+                this.missingVersionBlock = XBlock.initializeBlock($('#missing-version')[0]);
+                this.missingInitBlock = XBlock.initializeBlock($('#missing-init')[0]);
+            });
+
+            it('loads the right runtime version', function() {
+                expect(window.TestRuntime.vA).toHaveBeenCalledWith();
+                expect(window.TestRuntime.vZ).toHaveBeenCalledWith();
+            });
+
+            it('loads the right init function', function() {
+                expect(window.initFnA).toHaveBeenCalledWith(this.runtimeA, this.vANode, {});
+                expect(window.initFnZ).toHaveBeenCalledWith(this.runtimeZ, this.vZNode, {});
+            });
+
+            it('loads when missing versions', function() {
+                expect(this.missingVersionBlock.element).toBe($('#missing-version')[0]);
+                expect(this.missingVersionBlock.name).toBe('no-version');
+            });
+
+            it('loads when missing init fn', function() {
+                expect(this.missingInitBlock.element).toBe($('#missing-init')[0]);
+                expect(this.missingInitBlock.name).toBe('no-init');
+            });
+
+            it('adds names to blocks', function() {
+                expect(this.vABlock.name).toBe('a-name');
+            });
+
+            it('leaves leaves missing names undefined', function() {
+                expect(this.vZBlock.name).toBeUndefined();
+            });
+
+            it('attaches the element to the block', function() {
+                expect(this.vABlock.element).toBe(this.vANode);
+                expect(this.vZBlock.element).toBe(this.vZNode);
+                expect(this.missingVersionBlock.element).toBe($('#missing-version')[0]);
+                expect(this.missingInitBlock.element).toBe($('#missing-init')[0]);
+            });
+
+            it('passes through the request token', function() {
+                expect(XBlock.initializeXBlocks).toHaveBeenCalledWith($(this.vANode), 'req-token-a');
+                expect(XBlock.initializeXBlocks).toHaveBeenCalledWith($(this.vZNode), 'req-token-z');
+            });
+        });
+        describe('initializeBlocks', function() {
+            beforeEach(function() {
+                spyOn(XBlock, 'initializeBlock');
+                this.vANode = $('#vA')[0];
+                this.vZNode = $('#vZ')[0];
+            });
+
+            it('initializes children', function() {
+                XBlock.initializeBlocks($('#jasmine-fixtures'));
+                expect(XBlock.initializeBlock).toHaveBeenCalledWith(this.vANode, void 0);
+                expect(XBlock.initializeBlock).toHaveBeenCalledWith(this.vZNode, void 0);
+            });
+
+            it('only initializes matching request tokens', function() {
+                XBlock.initializeBlocks($('#jasmine-fixtures'), 'req-token-z');
+                expect(XBlock.initializeBlock).not.toHaveBeenCalledWith(this.vANode, jasmine.any(Object));
+                expect(XBlock.initializeBlock).toHaveBeenCalledWith(this.vZNode, 'req-token-z');
+            });
+        });
+    });
+}).call(this);
diff --git a/common/static/common/js/spec/xblock/runtime.v1_spec.js b/common/static/common/js/spec/xblock/runtime.v1_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..49ea310e97681775d886bb8b8c8b37f66b3b5c67
--- /dev/null
+++ b/common/static/common/js/spec/xblock/runtime.v1_spec.js
@@ -0,0 +1,28 @@
+(function() {
+    'use strict';
+
+    describe('XBlock.Runtime.v1', function() {
+        beforeEach(function() {
+            setFixtures('<div class="xblock" data-handler-prefix="/xblock/fake-usage-id/handler"/>');
+            this.children = [
+                {
+                    name: 'childA'
+                }, {
+                    name: 'childB'
+                }
+            ];
+            this.element = $('.xblock')[0];
+            $(this.element).prop('xblock_children', this.children);
+            this.runtime = new XBlock.Runtime.v1(this.element);
+        });
+
+        it('provides a list of children', function() {
+            expect(this.runtime.children(this.element)).toBe(this.children);
+        });
+
+        it('maps children by name', function() {
+            expect(this.runtime.childMap(this.element, 'childA')).toBe(this.children[0]);
+            expect(this.runtime.childMap(this.element, 'childB')).toBe(this.children[1]);
+        });
+    });
+}).call(this);
diff --git a/common/static/js/xblock/core.js b/common/static/common/js/xblock/core.js
similarity index 78%
rename from common/static/js/xblock/core.js
rename to common/static/common/js/xblock/core.js
index 0cbaac3209200a26478ad5b2146ea686c8113a93..02c6ef31f868b7fe232581a093a849d42c05045a 100644
--- a/common/static/js/xblock/core.js
+++ b/common/static/common/js/xblock/core.js
@@ -1,13 +1,15 @@
 (function($, JSON) {
-
     'use strict';
 
-    function initializeBlockLikes(block_class, initializer, element, requestToken) {
-        var requestToken = requestToken || $(element).data('request-token');
+    var XBlock;
+
+    function initializeBlockLikes(blockClass, initializer, element, requestToken) {
+        var selector;
+        requestToken = requestToken || $(element).data('request-token');
         if (requestToken) {
-            var selector = '.' + block_class + '[data-request-token="' + requestToken + '"]';
+            selector = '.' + blockClass + '[data-request-token="' + requestToken + '"]';
         } else {
-            var selector = '.' + block_class;
+            selector = '.' + blockClass;
         }
         return $(element).immediateDescendents(selector).map(function(idx, elem) {
             return initializer(elem, requestToken);
@@ -15,17 +17,19 @@
     }
 
     function elementRuntime(element) {
-        var $element = $(element);
-        var runtime = $element.data('runtime-class');
-        var version = $element.data('runtime-version');
-        var initFnName = $element.data('init');
+        var $element = $(element),
+            runtime = $element.data('runtime-class'),
+            version = $element.data('runtime-version'),
+            initFnName = $element.data('init');
 
         if (runtime && version && initFnName) {
-            return new window[runtime]['v' + version];
+            return new window[runtime]['v' + version]();
         } else {
             if (runtime || version || initFnName) {
-                var elementTag = $('<div>').append($element.clone()).html();
-                console.log('Block ' + elementTag + ' is missing data-runtime, data-runtime-version or data-init, and can\'t be initialized');
+                console.log(
+                    'Block ' + $element.outerHTML + ' is missing data-runtime, data-runtime-version or data-init, ' +
+                    'and can\'t be initialized'
+                );
             } // else this XBlock doesn't have a JS init function.
             return null;
         }
@@ -42,14 +46,13 @@
      * The constructor is called with the arguments 'runtime', 'element',
      * and then all of 'block_args'.
      */
-    function constructBlock(element, block_args) {
+    function constructBlock(element, blockArgs) {
         var block;
         var $element = $(element);
         var runtime = elementRuntime(element);
 
-        block_args.unshift(element);
-        block_args.unshift(runtime);
-
+        blockArgs.unshift(element);
+        blockArgs.unshift(runtime);
 
         if (runtime) {
 
@@ -59,7 +62,7 @@
                 // This create a new constructor that can then apply() the block_args
                 // to the initFn.
                 function Block() {
-                    return initFn.apply(this, block_args);
+                    return initFn.apply(this, blockArgs);
                 }
                 Block.prototype = initFn.prototype;
 
@@ -78,7 +81,7 @@
         return block;
     }
 
-    var XBlock = {
+    XBlock = {
         Runtime: {},
 
         /**
@@ -88,11 +91,12 @@
          * the children themselves.
          */
         initializeBlock: function(element, requestToken) {
-            var $element = $(element);
+            var $element = $(element),
+                children, asides;
 
-            var requestToken = requestToken || $element.data('request-token');
-            var children = XBlock.initializeXBlocks($element, requestToken);
-            var asides = XBlock.initializeXBlockAsides($element, requestToken);
+            requestToken = requestToken || $element.data('request-token');
+            children = XBlock.initializeXBlocks($element, requestToken);
+            asides = XBlock.initializeXBlockAsides($element, requestToken);
             if (asides) {
                 children = children.concat(asides);
             }
@@ -106,7 +110,7 @@
          * If requestToken is omitted, use the data-request-token attribute from element, or use
          * the request-tokens specified on the children themselves.
          */
-        initializeAside: function(element, requestToken) {
+        initializeAside: function(element) {
             var blockUsageId = $(element).data('block-id');
             var blockElement = $(element).siblings('[data-usage-id="' + blockUsageId + '"]')[0];
             return constructBlock(element, [blockElement, initArgs(element)]);
diff --git a/common/static/common/js/xblock/runtime.v1.js b/common/static/common/js/xblock/runtime.v1.js
new file mode 100644
index 0000000000000000000000000000000000000000..1c196cb1ec58bbeeb6a8833b1430d6b4438652e0
--- /dev/null
+++ b/common/static/common/js/xblock/runtime.v1.js
@@ -0,0 +1,50 @@
+(function() {
+    'use strict';
+
+    XBlock.Runtime.v1 = (function() {
+
+        function v1() {
+            var _this = this;
+            this.childMap = function() {
+                return v1.prototype.childMap.apply(_this, arguments);
+            };
+            this.children = function() {
+                return v1.prototype.children.apply(_this, arguments);
+            };
+        }
+
+        v1.prototype.children = function(block) {
+            return $(block).prop('xblock_children');
+        };
+
+        v1.prototype.childMap = function(block, childName) {
+            var child, _i, _len, _ref;
+            _ref = this.children(block);
+            for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+                child = _ref[_i];
+                if (child.name === childName) {
+                    return child;
+                }
+            }
+        };
+
+        /**
+         * Notify the client-side runtime that an event has occurred.
+         *
+         * This allows the runtime to update the UI in a consistent way
+         * for different XBlocks.
+         * `name` is an arbitrary string (for example, "save")
+         * `data` is an object (for example, {state: 'starting'})
+         * The default implementation is a no-op.
+         *
+         * WARNING: This is an interim solution and not officially supported!
+         */
+        v1.prototype.notify = function() {
+            // Do nothing
+        };
+
+        return v1;
+
+    })();
+
+}).call(this);
diff --git a/common/static/karma_common.conf.js b/common/static/karma_common.conf.js
index cf19a73fd72cd63d32b43ad733134d5266bf850e..9a1c333b9d8f073c5fd2d18d3544701037453609 100644
--- a/common/static/karma_common.conf.js
+++ b/common/static/karma_common.conf.js
@@ -11,7 +11,7 @@ var options = {
 
     useRequireJs: false,
 
-    normalizePathsForCoverageFunc: function (appRoot, pattern) {
+    normalizePathsForCoverageFunc: function(appRoot, pattern) {
         return path.join(appRoot, '/common/static/' + pattern);
     },
 
@@ -50,16 +50,17 @@ var options = {
     // Make sure the patterns in sourceFiles and specFiles do not match the same file.
     // Otherwise Istanbul which is used for coverage tracking will cause tests to not run.
     sourceFiles: [
-        {pattern: 'js/xblock/**/*.js', included: true},
         {pattern: 'coffee/src/**/*.js', included: true},
-        {pattern: 'js/src/**/*.js', included: true},
-        {pattern: 'js/capa/src/**/*.js', included: true}
+        {pattern: 'common/js/xblock/core.js', included: true},
+        {pattern: 'common/js/xblock/runtime.v1.js', included: true},
+        {pattern: 'js/capa/src/**/*.js', included: true},
+        {pattern: 'js/src/**/*.js', included: true}
     ],
 
     specFiles: [
         {pattern: 'coffee/spec/**/*.js', included: true},
-        {pattern: 'js/spec/**/*.js', included: true},
-        {pattern: 'js/capa/spec/**/*.js', included: true}
+        {pattern: 'common/js/spec/xblock/*.js', included: true},
+        {pattern: 'js/**/*spec.js', included: true}
     ],
 
     fixtureFiles: [
@@ -69,6 +70,6 @@ var options = {
     ]
 };
 
-module.exports = function (config) {
+module.exports = function(config) {
     configModule.configure(config, options);
 };
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 5d8db0c6db9922576ae76699bc9196f87d36c941..5ea1ecac1dd05d4f1bba4e00c107726171019b24 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -1528,9 +1528,14 @@ PIPELINE_CSS = {
 }
 
 
-common_js = set(rooted_glob(COMMON_ROOT / 'static', 'coffee/src/**/*.js')) - set(courseware_js + discussion_js + notes_js + instructor_dash_js)    # pylint: disable=line-too-long
-project_js = set(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/**/*.js')) - set(courseware_js + discussion_js + notes_js + instructor_dash_js)  # pylint: disable=line-too-long
-
+separately_bundled_js = set(courseware_js + discussion_js + notes_js + instructor_dash_js)
+common_js = sorted(set(rooted_glob(COMMON_ROOT / 'static', 'coffee/src/**/*.js')) - separately_bundled_js)
+xblock_runtime_js = [
+    'common/js/xblock/core.js',
+    'common/js/xblock/runtime.v1.js',
+    'lms/js/xblock/lms.runtime.v1.js',
+]
+lms_application_js = sorted(set(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/**/*.js')) - separately_bundled_js)
 
 PIPELINE_JS = {
     'base_application': {
@@ -1539,13 +1544,14 @@ PIPELINE_JS = {
     },
 
     'application': {
-
-        # Application will contain all paths not in courseware_only_js
-        'source_filenames': ['js/xblock/core.js'] + sorted(common_js) + sorted(project_js) + base_application_js + [
-            'js/sticky_filter.js',
-            'js/query-params.js',
-            'js/vendor/moment.min.js',
-        ],
+        'source_filenames': (
+            common_js + xblock_runtime_js + base_application_js + lms_application_js +
+            [
+                'js/sticky_filter.js',
+                'js/query-params.js',
+                'js/vendor/moment.min.js',
+            ]
+        ),
         'output_filename': 'js/lms-application.js',
     },
     'proctoring': {
diff --git a/lms/static/coffee/src/xblock/lms.runtime.v1.coffee b/lms/static/coffee/src/xblock/lms.runtime.v1.coffee
deleted file mode 100644
index f10b1ae1c506d7e018b0753eb2c845f1a6b700b5..0000000000000000000000000000000000000000
--- a/lms/static/coffee/src/xblock/lms.runtime.v1.coffee
+++ /dev/null
@@ -1,18 +0,0 @@
-@LmsRuntime = {}
-
-class LmsRuntime.v1 extends XBlock.Runtime.v1
-  handlerUrl: (element, handlerName, suffix, query, thirdparty) ->
-    courseId = $(element).data("course-id")
-    usageId = $(element).data("usage-id")
-    handlerAuth = if thirdparty then "handler_noauth" else "handler"
-
-    uri = URI('/courses').segment(courseId)
-                         .segment('xblock')
-                         .segment(usageId)
-                         .segment(handlerAuth)
-                         .segment(handlerName)
-
-    if suffix? then uri.segment(suffix)
-    if query? then uri.search(query)
-
-    uri.toString()
diff --git a/lms/static/karma_lms_coffee.conf.js b/lms/static/karma_lms_coffee.conf.js
index aab58949281de9cf5cb83145c707354c18cd3a29..78866c684c879e3a22beb7cf9a931a78ecd12678 100644
--- a/lms/static/karma_lms_coffee.conf.js
+++ b/lms/static/karma_lms_coffee.conf.js
@@ -29,6 +29,7 @@ var options = {
         {pattern: 'common/js/vendor/jquery.js', included: true},
         {pattern: 'common/js/vendor/jquery-migrate.js', included: true},
         {pattern: 'common/js/vendor/underscore.js', included: true},
+        {pattern: 'common/js/xblock/*.js', included: true},
         {pattern: 'xmodule_js/common_static/js/src/logger.js', included: true},
         {pattern: 'xmodule_js/common_static/js/test/i18n.js', included: true},
         {pattern: 'xmodule_js/common_static/js/vendor/CodeMirror/codemirror.js', included: true},
@@ -38,8 +39,6 @@ var options = {
         {pattern: 'xmodule_js/common_static/js/vendor/jquery-ui.min.js', included: true},
         {pattern: 'xmodule_js/common_static/js/vendor/URI.min.js', included: true},
 
-        {pattern: 'xmodule_js/common_static/js/xblock/*.js', included: true},
-        {pattern: 'xmodule_js/common_static/coffee/src/xblock/*.js', included: true},
         {pattern: 'xmodule_js/src/capa/*.js', included: true},
         {pattern: 'xmodule_js/src/video/*.js', included: true},
         {pattern: 'xmodule_js/src/xmodule.js', included: true},
diff --git a/lms/static/lms/js/require-config.js b/lms/static/lms/js/require-config.js
index a1f37133f4b8f625ad30e3daaf469fed6f5ccc9f..bda10131a461dbcfa880aff443abae63e66a4d6f 100644
--- a/lms/static/lms/js/require-config.js
+++ b/lms/static/lms/js/require-config.js
@@ -1,4 +1,5 @@
-;(function (require, define) {
+;(function(require, define) {
+    'use strict';
 
     // We do not wish to bundle common libraries (that may also be used by non-RequireJS code on the page
     // into the optimized files. Therefore load these libraries through script tags and explicitly define them.
@@ -24,207 +25,207 @@
                 }
             }
             else {
-                console.error("Expected library to be included on page, but not found on window object: " + name);
+                console.error('Expected library to be included on page, but not found on window object: ' + name);
             }
         };
-        defineDependency("jQuery", "jquery");
-        defineDependency("jQuery", "jquery-migrate");
-        defineDependency("_", "underscore");
-        defineDependency("s", "underscore.string");
-        // Underscore.string no longer installs itself directly on "_". For compatibility with existing
-        // code, add it to "_" with its previous name.
+        defineDependency('jQuery', 'jquery');
+        defineDependency('jQuery', 'jquery-migrate');
+        defineDependency('_', 'underscore');
+        defineDependency('s', 'underscore.string');
+        // Underscore.string no longer installs itself directly on '_'. For compatibility with existing
+        // code, add it to '_' with its previous name.
         if (window._ && window.s) {
             window._.str = window.s;
         }
-        defineDependency("gettext", "gettext");
-        defineDependency("Logger", "logger");
-        defineDependency("URI", "URI");
-        defineDependency("Backbone", "backbone");
-        defineDependency("Modernizr", "modernizr");
+        defineDependency('gettext', 'gettext');
+        defineDependency('Logger', 'logger');
+        defineDependency('URI', 'URI');
+        defineDependency('Backbone', 'backbone');
+        defineDependency('Modernizr', 'modernizr');
 
-        // Add the UI Toolkit helper classes that have been installed in the "edx" namespace
-        defineDependency("edx.HtmlUtils", "edx-ui-toolkit/js/utils/html-utils");
-        defineDependency("edx.StringUtils", "edx-ui-toolkit/js/utils/string-utils");
+        // Add the UI Toolkit helper classes that have been installed in the 'edx' namespace
+        defineDependency('edx.HtmlUtils', 'edx-ui-toolkit/js/utils/html-utils');
+        defineDependency('edx.StringUtils', 'edx-ui-toolkit/js/utils/string-utils');
 
         // utility.js adds two functions to the window object, but does not return anything
-        defineDependency("isExternal", "utility", true);
+        defineDependency('isExternal', 'utility', true);
     }
 
     require.config({
         // NOTE: baseUrl has been previously set in lms/templates/main.html
         waitSeconds: 60,
         paths: {
-            "annotator_1.2.9": "js/vendor/edxnotes/annotator-full.min",
-            "date": "js/vendor/date",
-            "moment": "js/vendor/moment.min",
-            "moment-with-locales": "xmodule_js/common_static/js/vendor/moment-with-locales.min",
-            "text": "js/vendor/requirejs/text",
-            "logger": "js/src/logger",
-            "backbone": "common/js/vendor/backbone",
-            "backbone-super": "js/vendor/backbone-super",
-            "backbone.paginator": "common/js/vendor/backbone.paginator",
-            "underscore": "common/js/vendor/underscore",
-            "underscore.string": "common/js/vendor/underscore.string",
+            'annotator_1.2.9': 'js/vendor/edxnotes/annotator-full.min',
+            'date': 'js/vendor/date',
+            'moment': 'js/vendor/moment.min',
+            'moment-with-locales': 'xmodule_js/common_static/js/vendor/moment-with-locales.min',
+            'text': 'js/vendor/requirejs/text',
+            'logger': 'js/src/logger',
+            'backbone': 'common/js/vendor/backbone',
+            'backbone-super': 'js/vendor/backbone-super',
+            'backbone.paginator': 'common/js/vendor/backbone.paginator',
+            'underscore': 'common/js/vendor/underscore',
+            'underscore.string': 'common/js/vendor/underscore.string',
             // The jquery-migrate library was added in upgrading from
             // jQuery 1.7.x to 2.2.x.  This config allows developers
-            // to depend on "jquery" which opaquely requires both
+            // to depend on 'jquery' which opaquely requires both
             // libraries.
-            "jquery": "common/js/vendor/jquery",
-            "jquery-migrate": "common/js/vendor/jquery-migrate",
-            "jquery.scrollTo": "common/js/vendor/jquery.scrollTo",
-            "jquery.cookie": "js/vendor/jquery.cookie",
+            'jquery': 'common/js/vendor/jquery',
+            'jquery-migrate': 'common/js/vendor/jquery-migrate',
+            'jquery.scrollTo': 'common/js/vendor/jquery.scrollTo',
+            'jquery.cookie': 'js/vendor/jquery.cookie',
             'jquery.timeago': 'js/vendor/jquery.timeago',
-            "jquery.url": "js/vendor/url.min",
-            "jquery.ui": "js/vendor/jquery-ui.min",
-            "jquery.iframe-transport": "js/vendor/jQuery-File-Upload/js/jquery.iframe-transport",
-            "jquery.fileupload": "js/vendor/jQuery-File-Upload/js/jquery.fileupload",
-            "URI": "js/vendor/URI.min",
-            "string_utils": "js/src/string_utils",
-            "utility": "js/src/utility",
-            "modernizr": "edx-pattern-library/js/modernizr-custom",
-            "afontgarde": "edx-pattern-library/js/afontgarde",
-            "edxicons": "edx-pattern-library/js/edx-icons",
-            "draggabilly": "js/vendor/draggabilly",
+            'jquery.url': 'js/vendor/url.min',
+            'jquery.ui': 'js/vendor/jquery-ui.min',
+            'jquery.iframe-transport': 'js/vendor/jQuery-File-Upload/js/jquery.iframe-transport',
+            'jquery.fileupload': 'js/vendor/jQuery-File-Upload/js/jquery.fileupload',
+            'URI': 'js/vendor/URI.min',
+            'string_utils': 'js/src/string_utils',
+            'utility': 'js/src/utility',
+            'modernizr': 'edx-pattern-library/js/modernizr-custom',
+            'afontgarde': 'edx-pattern-library/js/afontgarde',
+            'edxicons': 'edx-pattern-library/js/edx-icons',
+            'draggabilly': 'js/vendor/draggabilly',
 
             // Files needed by OVA
-            "annotator": "js/vendor/ova/annotator-full",
-            "annotator-harvardx": "js/vendor/ova/annotator-full-firebase-auth",
-            "video.dev": "js/vendor/ova/video.dev",
-            "vjs.youtube": "js/vendor/ova/vjs.youtube",
-            "rangeslider": "js/vendor/ova/rangeslider",
-            "share-annotator": "js/vendor/ova/share-annotator",
-            "richText-annotator": "js/vendor/ova/richText-annotator",
-            "reply-annotator": "js/vendor/ova/reply-annotator",
-            "grouping-annotator": "js/vendor/ova/grouping-annotator",
-            "tags-annotator": "js/vendor/ova/tags-annotator",
-            "diacritic-annotator": "js/vendor/ova/diacritic-annotator",
-            "flagging-annotator": "js/vendor/ova/flagging-annotator",
-            "jquery-Watch": "js/vendor/ova/jquery-Watch",
-            "openseadragon": "js/vendor/ova/openseadragon",
-            "osda": "js/vendor/ova/OpenSeaDragonAnnotation",
-            "ova": "js/vendor/ova/ova",
-            "catch": "js/vendor/ova/catch/js/catch",
-            "handlebars": "js/vendor/ova/catch/js/handlebars-1.1.2",
-            "tinymce": "js/vendor/tinymce/js/tinymce/tinymce.full.min",
-            "jquery.tinymce": "js/vendor/tinymce/js/tinymce/jquery.tinymce.min",
-            "picturefill": "common/js/vendor/picturefill"
+            'annotator': 'js/vendor/ova/annotator-full',
+            'annotator-harvardx': 'js/vendor/ova/annotator-full-firebase-auth',
+            'video.dev': 'js/vendor/ova/video.dev',
+            'vjs.youtube': 'js/vendor/ova/vjs.youtube',
+            'rangeslider': 'js/vendor/ova/rangeslider',
+            'share-annotator': 'js/vendor/ova/share-annotator',
+            'richText-annotator': 'js/vendor/ova/richText-annotator',
+            'reply-annotator': 'js/vendor/ova/reply-annotator',
+            'grouping-annotator': 'js/vendor/ova/grouping-annotator',
+            'tags-annotator': 'js/vendor/ova/tags-annotator',
+            'diacritic-annotator': 'js/vendor/ova/diacritic-annotator',
+            'flagging-annotator': 'js/vendor/ova/flagging-annotator',
+            'jquery-Watch': 'js/vendor/ova/jquery-Watch',
+            'openseadragon': 'js/vendor/ova/openseadragon',
+            'osda': 'js/vendor/ova/OpenSeaDragonAnnotation',
+            'ova': 'js/vendor/ova/ova',
+            'catch': 'js/vendor/ova/catch/js/catch',
+            'handlebars': 'js/vendor/ova/catch/js/handlebars-1.1.2',
+            'tinymce': 'js/vendor/tinymce/js/tinymce/tinymce.full.min',
+            'jquery.tinymce': 'js/vendor/tinymce/js/tinymce/jquery.tinymce.min',
+            'picturefill': 'common/js/vendor/picturefill'
             // end of files needed by OVA
         },
         shim: {
-            "annotator_1.2.9": {
-                deps: ["jquery"],
-                exports: "Annotator"
+            'annotator_1.2.9': {
+                deps: ['jquery'],
+                exports: 'Annotator'
             },
-            "date": {
-                exports: "Date"
+            'date': {
+                exports: 'Date'
             },
-            "jquery": {
-                exports: "jQuery"
+            'jquery': {
+                exports: 'jQuery'
             },
-            "jquery-migrate": ['jquery'],
-            "jquery.cookie": {
-                deps: ["jquery"],
-                exports: "jQuery.fn.cookie"
+            'jquery-migrate': ['jquery'],
+            'jquery.cookie': {
+                deps: ['jquery'],
+                exports: 'jQuery.fn.cookie'
             },
-            "jquery.timeago": {
-                deps: ["jquery"],
-                exports: "jQuery.timeago"
+            'jquery.timeago': {
+                deps: ['jquery'],
+                exports: 'jQuery.timeago'
             },
-            "jquery.url": {
-                deps: ["jquery"],
-                exports: "jQuery.url"
+            'jquery.url': {
+                deps: ['jquery'],
+                exports: 'jQuery.url'
             },
-            "jquery.fileupload": {
-                deps: ["jquery.ui", "jquery.iframe-transport"],
-                exports: "jQuery.fn.fileupload"
+            'jquery.fileupload': {
+                deps: ['jquery.ui', 'jquery.iframe-transport'],
+                exports: 'jQuery.fn.fileupload'
             },
-            "jquery.tinymce": {
-                deps: ["jquery", "tinymce"],
-                exports: "jQuery.fn.tinymce"
+            'jquery.tinymce': {
+                deps: ['jquery', 'tinymce'],
+                exports: 'jQuery.fn.tinymce'
             },
-            "backbone.paginator": {
-                deps: ["backbone"],
-                exports: "Backbone.PageableCollection"
+            'backbone.paginator': {
+                deps: ['backbone'],
+                exports: 'Backbone.PageableCollection'
             },
-            "backbone-super": {
-                deps: ["backbone"]
+            'backbone-super': {
+                deps: ['backbone']
             },
-            "string_utils": {
-                deps: ["underscore"],
-                exports: "interpolate_text"
+            'string_utils': {
+                deps: ['underscore'],
+                exports: 'interpolate_text'
             },
             // Needed by OVA
-            "video.dev": {
-                exports:"videojs"
+            'video.dev': {
+                exports: 'videojs'
             },
-            "vjs.youtube": {
-                deps: ["video.dev"]
+            'vjs.youtube': {
+                deps: ['video.dev']
             },
-            "rangeslider": {
-                deps: ["video.dev"]
+            'rangeslider': {
+                deps: ['video.dev']
             },
-            "annotator": {
-                exports: "Annotator"
+            'annotator': {
+                exports: 'Annotator'
             },
-            "annotator-harvardx":{
-                deps: ["annotator"]
+            'annotator-harvardx': {
+                deps: ['annotator']
             },
-            "share-annotator": {
-                deps: ["annotator"]
+            'share-annotator': {
+                deps: ['annotator']
             },
-            "richText-annotator": {
-                deps: ["annotator", "tinymce"]
+            'richText-annotator': {
+                deps: ['annotator', 'tinymce']
             },
-            "reply-annotator": {
-                deps: ["annotator"]
+            'reply-annotator': {
+                deps: ['annotator']
             },
-            "tags-annotator": {
-                deps: ["annotator"]
+            'tags-annotator': {
+                deps: ['annotator']
             },
-            "diacritic-annotator": {
-              deps: ["annotator"]
+            'diacritic-annotator': {
+                deps: ['annotator']
             },
-            "flagging-annotator": {
-                deps: ["annotator"]
+            'flagging-annotator': {
+                deps: ['annotator']
             },
-            "grouping-annotator": {
-                deps: ["annotator"]
+            'grouping-annotator': {
+                deps: ['annotator']
             },
-            "ova": {
-                exports: "ova",
+            'ova': {
+                exports: 'ova',
                 deps: [
-                    "annotator", "annotator-harvardx", "video.dev", "vjs.youtube", "rangeslider", "share-annotator",
-                    "richText-annotator", "reply-annotator", "tags-annotator", "flagging-annotator",
-                    "grouping-annotator", "diacritic-annotator", "jquery-Watch", "catch", "handlebars", "URI"
+                    'annotator', 'annotator-harvardx', 'video.dev', 'vjs.youtube', 'rangeslider', 'share-annotator',
+                    'richText-annotator', 'reply-annotator', 'tags-annotator', 'flagging-annotator',
+                    'grouping-annotator', 'diacritic-annotator', 'jquery-Watch', 'catch', 'handlebars', 'URI'
                 ]
             },
-            "osda": {
-                exports: "osda",
+            'osda': {
+                exports: 'osda',
                 deps: [
-                    "annotator", "annotator-harvardx", "video.dev", "vjs.youtube", "rangeslider", "share-annotator",
-                    "richText-annotator", "reply-annotator", "tags-annotator", "flagging-annotator",
-                    "grouping-annotator", "diacritic-annotator", "openseadragon", "jquery-Watch", "catch", "handlebars",
-                    "URI"
+                    'annotator', 'annotator-harvardx', 'video.dev', 'vjs.youtube', 'rangeslider', 'share-annotator',
+                    'richText-annotator', 'reply-annotator', 'tags-annotator', 'flagging-annotator',
+                    'grouping-annotator', 'diacritic-annotator', 'openseadragon', 'jquery-Watch', 'catch', 'handlebars',
+                    'URI'
                 ]
             },
-            "tinymce": {
-                exports: "tinymce"
+            'tinymce': {
+                exports: 'tinymce'
             },
             // End of needed by OVA
-            "moment": {
-                exports: "moment"
+            'moment': {
+                exports: 'moment'
             },
-            "moment-with-locales": {
-                exports: "moment"
+            'moment-with-locales': {
+                exports: 'moment'
             },
-            "afontgarde": {
-                exports: "AFontGarde"
+            'afontgarde': {
+                exports: 'AFontGarde'
             },
             // Because Draggabilly is being used by video code, the namespaced version of
             // require is not being recognized. Therefore the library is being added to the
             // global namespace instead of being registered in require.
-            "draggabilly": {
-                exports: "Draggabilly"
+            'draggabilly': {
+                exports: 'Draggabilly'
             }
         }
     });
diff --git a/lms/static/lms/js/spec/main.js b/lms/static/lms/js/spec/main.js
index 5d397d92953bb7f575c950ddfe32cfdf0b5c2eaa..6d58500cce713ad506d765dc893a5a62631a1a47 100644
--- a/lms/static/lms/js/spec/main.js
+++ b/lms/static/lms/js/spec/main.js
@@ -56,9 +56,8 @@
             'coffee/src/ajax_prefix': 'xmodule_js/common_static/coffee/src/ajax_prefix',
             'coffee/src/instructor_dashboard/student_admin': 'coffee/src/instructor_dashboard/student_admin',
             'xmodule_js/common_static/js/test/add_ajax_prefix': 'xmodule_js/common_static/js/test/add_ajax_prefix',
-            'xblock/core': 'xmodule_js/common_static/js/xblock/core',
-            'xblock/runtime.v1': 'xmodule_js/common_static/coffee/src/xblock/runtime.v1',
-            'xblock/lms.runtime.v1': 'coffee/src/xblock/lms.runtime.v1',
+            'xblock/lms.runtime.v1': 'lms/js/xblock/lms.runtime.v1',
+            'xblock': 'common/js/xblock',
             'capa/display': 'xmodule_js/src/capa/display',
             'string_utils': 'xmodule_js/common_static/js/src/string_utils',
             'logger': 'xmodule_js/common_static/js/src/logger',
diff --git a/lms/static/lms/js/xblock/lms.runtime.v1.js b/lms/static/lms/js/xblock/lms.runtime.v1.js
new file mode 100644
index 0000000000000000000000000000000000000000..50903ccaa39577383b682747bfaeaa14dfb0dd9e
--- /dev/null
+++ b/lms/static/lms/js/xblock/lms.runtime.v1.js
@@ -0,0 +1,55 @@
+(function(URI) {
+    'use strict';
+
+    var __hasProp = {}.hasOwnProperty,
+        __extends = function(child, parent) {
+            var key;
+            for (key in parent) {
+                if (__hasProp.call(parent, key)) {
+                    child[key] = parent[key];
+                }
+            }
+            function Ctor() {
+                this.constructor = child;
+            }
+            Ctor.prototype = parent.prototype;
+            child.prototype = new Ctor();
+            child.__super__ = parent.prototype;
+            return child;
+        };
+
+    this.LmsRuntime = {};
+
+    this.LmsRuntime.v1 = (function(_super) {
+
+        __extends(v1, _super);
+
+        function v1() {
+            return v1.__super__.constructor.apply(this, arguments);
+        }
+
+        v1.prototype.handlerUrl = function(element, handlerName, suffix, query, thirdparty) {
+            var courseId, handlerAuth, uri, usageId;
+            courseId = $(element).data('course-id');
+            usageId = $(element).data('usage-id');
+            handlerAuth = thirdparty ? 'handler_noauth' : 'handler';
+            uri = URI('/courses')
+                .segment(courseId)
+                .segment('xblock')
+                .segment(usageId)
+                .segment(handlerAuth)
+                .segment(handlerName);
+            if (suffix !== null) {
+                uri.segment(suffix);
+            }
+            if (query !== null) {
+                uri.search(query);
+            }
+            return uri.toString();
+        };
+
+        return v1;
+
+    })(XBlock.Runtime.v1);
+
+}).call(this, URI);  // jshint ignore:line