diff --git a/.babelrc b/.babelrc index e1122c2c6beb..62ffd16c8d2b 100644 --- a/.babelrc +++ b/.babelrc @@ -1,21 +1,16 @@ { "presets": [ - ["env", { - "targets": { - "browsers": [ - "last 2 versions", - "IE >= 11" - ] - }, - "useBuiltIns": true, - "modules": false, - "exclude": [ - "transform-regenerator" - ] - }], - "react" - ], - "plugins": [ - "transform-object-rest-spread" + [ + "env", + { + "targets": { + "browsers": [ + "last 2 versions", + "IE >= 11" + ] + }, + "modules": "commonjs" + } + ] ] } diff --git a/.eslintignore b/.eslintignore index 7bec88e2cb3e..8277bea27284 100644 --- a/.eslintignore +++ b/.eslintignore @@ -58,3 +58,5 @@ common/lib/xmodule/xmodule/js/src/vertical/edit.js # This file is responsible for almost half of the repo's total issues. common/lib/xmodule/xmodule/js/src/capa/schematic.js + +!**/.eslintrc.js diff --git a/common/static/common/js/karma.common.conf.js b/common/static/common/js/karma.common.conf.js index ee9359fcd5ec..38ded3056d7c 100644 --- a/common/static/common/js/karma.common.conf.js +++ b/common/static/common/js/karma.common.conf.js @@ -40,6 +40,9 @@ var path = require('path'); var _ = require('underscore'); var appRoot = path.join(__dirname, '../../../../'); +var webpackConfig = require(path.join(appRoot, 'webpack.config.js')); + +delete webpackConfig.entry; // Files which are needed by all lms/cms suites. var commonFiles = { @@ -177,43 +180,32 @@ var defaultNormalizeFunc = function(appRoot, pattern) { return pattern; }; -var normalizePathsForCoverage = function(files, normalizeFunc) { +var normalizePathsForCoverage = function(files, normalizeFunc, preprocessors) { var normalizeFn = normalizeFunc || defaultNormalizeFunc, + normalizedFile, filesForCoverage = {}; files.forEach(function(file) { if (!file.ignoreCoverage) { - filesForCoverage[normalizeFn(appRoot, file.pattern)] = ['coverage']; + normalizedFile = normalizeFn(appRoot, file.pattern); + filesForCoverage[normalizedFile] = ['coverage'].concat(preprocessors[normalizedFile] || []); } }); return filesForCoverage; }; -/** - * Sets nocache on each file in the list. - * @param {Object} files - * @param {Bool} enable - * @return {Object} - */ -var setNocache = function(files, enable) { - files.forEach(function(f) { - if (_.isObject(f)) { - f.nocache = enable; - } - }); - return files; -}; - /** * Sets defaults for each file pattern. + * RequireJS files are excluded by default. + * Webpack files are included by default. * @param {Object} files * @return {Object} */ var setDefaults = function(files) { return files.map(function(f) { var file = _.isObject(f) ? f : {pattern: f}; - if (!file.included) { + if (!file.included && !file.webpack) { f.included = false; } return file; @@ -281,6 +273,8 @@ var getBaseConfig = function(config, useRequireJs) { 'karma-chrome-launcher', 'karma-firefox-launcher', 'karma-spec-reporter', + 'karma-webpack', + 'karma-sourcemap-loader', customPlugin ], @@ -349,7 +343,9 @@ var getBaseConfig = function(config, useRequireJs) { client: { captureConsole: false - } + }, + + webpack: webpackConfig }; }; @@ -382,11 +378,6 @@ var configure = function(config, options) { // We set it to false by default because RequireJS should be used instead. files = setDefaults(files); - // With nocache=true, Karma always serves the latest files from disk. - // However, that prevents coverage tracking from working. - // So we only set it if coverage tracking is off. - setNocache(files, !config.coverage); - var filesForCoverage = _.flatten( _.map( ['sourceFiles', 'specFiles'], @@ -399,7 +390,7 @@ var configure = function(config, options) { var preprocessors = _.extend( {}, options.preprocessors, - normalizePathsForCoverage(filesForCoverage, options.normalizePathsForCoverageFunc) + normalizePathsForCoverage(filesForCoverage, options.normalizePathsForCoverageFunc, options.preprocessors) ); config.set(_.extend(baseConfig, { diff --git a/lms/static/karma_lms.conf.js b/lms/static/karma_lms.conf.js index 388103836f6b..62ad5df5b905 100644 --- a/lms/static/karma_lms.conf.js +++ b/lms/static/karma_lms.conf.js @@ -28,7 +28,6 @@ var options = { sourceFiles: [ {pattern: 'coffee/src/**/!(*spec).js'}, {pattern: 'course_bookmarks/**/!(*spec).js'}, - {pattern: 'course_experience/js/**/!(*spec).js'}, {pattern: 'discussion/js/**/!(*spec).js'}, {pattern: 'js/**/!(*spec|djangojs).js'}, {pattern: 'lms/js/**/!(*spec).js'}, @@ -37,7 +36,8 @@ var options = { ], specFiles: [ - {pattern: '../**/*spec.js'} + {pattern: '../**/*spec.js'}, + {pattern: 'course_experience/js/**/*_spec.js', webpack: true} ], fixtureFiles: [ @@ -49,9 +49,17 @@ var options = { runFiles: [ {pattern: 'lms/js/spec/main.js', included: true} - ] + ], + + preprocessors: {} }; +options.specFiles + .filter(function(file) { return file.webpack; }) + .forEach(function(file) { + options.preprocessors[file.pattern] = ['webpack', 'sourcemap']; + }); + module.exports = function(config) { configModule.configure(config, options); }; diff --git a/lms/static/lms/js/spec/main.js b/lms/static/lms/js/spec/main.js index 2656462645c6..4ad059063aee 100644 --- a/lms/static/lms/js/spec/main.js +++ b/lms/static/lms/js/spec/main.js @@ -676,7 +676,6 @@ 'course_bookmarks/js/spec/bookmark_button_view_spec.js', 'course_bookmarks/js/spec/bookmarks_list_view_spec.js', 'course_bookmarks/js/spec/course_bookmarks_factory_spec.js', - 'course_experience/js/spec/course_outline_factory_spec.js', 'discussion/js/spec/discussion_board_factory_spec.js', 'discussion/js/spec/discussion_profile_page_factory_spec.js', 'discussion/js/spec/discussion_board_view_spec.js', diff --git a/openedx/features/course_experience/.eslintrc.js b/openedx/features/course_experience/.eslintrc.js new file mode 100644 index 000000000000..49519fe7263f --- /dev/null +++ b/openedx/features/course_experience/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + extends: 'eslint-config-edx', + root: true, +}; diff --git a/openedx/features/course_experience/static/course_experience/js/CourseOutline.js b/openedx/features/course_experience/static/course_experience/js/CourseOutline.js index 88fca6904e20..53915daa91bf 100644 --- a/openedx/features/course_experience/static/course_experience/js/CourseOutline.js +++ b/openedx/features/course_experience/static/course_experience/js/CourseOutline.js @@ -1,5 +1,5 @@ import * as constants from 'edx-ui-toolkit/src/js/utils/constants'; -import * as Logger from logger; +import log from 'logger'; export class CourseOutline { constructor(root) { @@ -8,19 +8,19 @@ export class CourseOutline { const currentFocusIndex = focusable.indexOf(event.target); switch (event.keyCode) { // eslint-disable-line default-case - case constants.keyCodes.down: - event.preventDefault(); - focusable[Math.min(currentFocusIndex + 1, focusable.length - 1)].focus(); - break; - case constants.keyCodes.up: - event.preventDefault(); - focusable[Math.max(currentFocusIndex - 1, 0)].focus(); - break; + case constants.keyCodes.down: + event.preventDefault(); + focusable[Math.min(currentFocusIndex + 1, focusable.length - 1)].focus(); + break; + case constants.keyCodes.up: + event.preventDefault(); + focusable[Math.max(currentFocusIndex - 1, 0)].focus(); + break; } }); document.querySelectorAll('a:not([href^="#"])').addEventListener('click', (event) => { - Logger.log( + log( 'edx.ui.lms.link_clicked', { current_url: window.location.href, diff --git a/openedx/features/course_experience/static/course_experience/js/spec/CourseOutline_spec.js b/openedx/features/course_experience/static/course_experience/js/spec/CourseOutline_spec.js new file mode 100644 index 000000000000..a2376cf11a5a --- /dev/null +++ b/openedx/features/course_experience/static/course_experience/js/spec/CourseOutline_spec.js @@ -0,0 +1,104 @@ +import * as constants from "edx-ui-toolkit/js/utils/constants"; +import log from 'logger'; +import { CourseOutline } from "../CourseOutline"; + +describe('Course outline factory', () => { + describe('keyboard listener', () => { + const triggerKeyListener = (current, destination, keyCode) => { + current.focus(); + spyOn(destination, 'focus'); + + $('.block-tree').trigger( + $.Event('keydown', { + keyCode, + target: current, + }), + ); + }; + + beforeEach(() => { + loadFixtures('course_experience/fixtures/course-outline-fragment.html'); + new CourseOutline('.block-tree'); + }); + + describe('when the down arrow is pressed', () => { + it('moves focus from a subsection to the next subsection in the outline', () => { + const current = $('a.focusable:contains("Homework - Labs and Demos")')[0]; + const destination = $('a.focusable:contains("Homework - Essays")')[0]; + + triggerKeyListener(current, destination, constants.keyCodes.down); + + expect(destination.focus).toHaveBeenCalled(); + }); + + it('moves focus to the section list if at a section boundary', () => { + const current = $('li.focusable:contains("Example Week 3: Be Social")')[0]; + const destination = $('ol.focusable:contains("Lesson 3 - Be Social")')[0]; + + triggerKeyListener(current, destination, constants.keyCodes.down); + + expect(destination.focus).toHaveBeenCalled(); + }); + + it('moves focus to the next section if on the last subsection', () => { + const current = $('a.focusable:contains("Homework - Essays")')[0]; + const destination = $('li.focusable:contains("Example Week 3: Be Social")')[0]; + + triggerKeyListener(current, destination, constants.keyCodes.down); + + expect(destination.focus).toHaveBeenCalled(); + }); + }); + + describe('when the up arrow is pressed', () => { + it('moves focus from a subsection to the previous subsection in the outline', () => { + const current = $('a.focusable:contains("Homework - Essays")')[0]; + const destination = $('a.focusable:contains("Homework - Labs and Demos")')[0]; + + triggerKeyListener(current, destination, constants.keyCodes.up); + + expect(destination.focus).toHaveBeenCalled(); + }); + + it('moves focus to the section group if at the first subsection', () => { + const current = $('a.focusable:contains("Lesson 3 - Be Social")')[0]; + const destination = $('ol.focusable:contains("Lesson 3 - Be Social")')[0]; + + triggerKeyListener(current, destination, constants.keyCodes.up); + + expect(destination.focus).toHaveBeenCalled(); + }); + + it('moves focus last subsection of the previous section if at a section boundary', () => { + const current = $('li.focusable:contains("Example Week 3: Be Social")')[0]; + const destination = $('a.focusable:contains("Homework - Essays")')[0]; + + triggerKeyListener(current, destination, constants.keyCodes.up); + + expect(destination.focus).toHaveBeenCalled(); + }); + }); + }); + + describe("eventing", function() { + beforeEach(function() { + loadFixtures("course_experience/fixtures/course-outline-fragment.html"); + CourseOutlineFactory(".block-tree"); + spyOn(Logger, "log"); + }); + + it("sends an event when an outline section is clicked", function() { + $('a.focusable:contains("Homework - Labs and Demos")').click(); + + expect(Logger.log).toHaveBeenCalledWith("edx.ui.lms.link_clicked", { + target_url: ( + window.location.origin + + "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type" + + "@sequential+block@graded_simulations" + ), + current_url: window.location.toString() + }); + }); + }); + +}); diff --git a/openedx/features/course_experience/static/course_experience/js/spec/course_outline_factory_spec.js b/openedx/features/course_experience/static/course_experience/js/spec/course_outline_factory_spec.js deleted file mode 100644 index 649de3ccd354..000000000000 --- a/openedx/features/course_experience/static/course_experience/js/spec/course_outline_factory_spec.js +++ /dev/null @@ -1,106 +0,0 @@ -define([ - 'jquery', - 'logger', - 'edx-ui-toolkit/js/utils/constants', - 'course_experience/js/course_outline_factory' -], - function($, Logger, constants, CourseOutlineFactory) { - 'use strict'; - - describe('Course outline factory', function() { - describe('keyboard listener', function() { - var triggerKeyListener = function(current, destination, keyCode) { - current.focus(); - spyOn(destination, 'focus'); - - $('.block-tree').trigger($.Event('keydown', { - keyCode: keyCode, - target: current - })); - }; - - beforeEach(function() { - loadFixtures('course_experience/fixtures/course-outline-fragment.html'); - CourseOutlineFactory('.block-tree'); - }); - - describe('when the down arrow is pressed', function() { - it('moves focus from a subsection to the next subsection in the outline', function() { - var current = $('a.focusable:contains("Homework - Labs and Demos")')[0], - destination = $('a.focusable:contains("Homework - Essays")')[0]; - - triggerKeyListener(current, destination, constants.keyCodes.down); - - expect(destination.focus).toHaveBeenCalled(); - }); - - it('moves focus to the section list if at a section boundary', function() { - var current = $('li.focusable:contains("Example Week 3: Be Social")')[0], - destination = $('ol.focusable:contains("Lesson 3 - Be Social")')[0]; - - triggerKeyListener(current, destination, constants.keyCodes.down); - - expect(destination.focus).toHaveBeenCalled(); - }); - - it('moves focus to the next section if on the last subsection', function() { - var current = $('a.focusable:contains("Homework - Essays")')[0], - destination = $('li.focusable:contains("Example Week 3: Be Social")')[0]; - - triggerKeyListener(current, destination, constants.keyCodes.down); - - expect(destination.focus).toHaveBeenCalled(); - }); - }); - - describe('when the up arrow is pressed', function() { - it('moves focus from a subsection to the previous subsection in the outline', function() { - var current = $('a.focusable:contains("Homework - Essays")')[0], - destination = $('a.focusable:contains("Homework - Labs and Demos")')[0]; - - triggerKeyListener(current, destination, constants.keyCodes.up); - - expect(destination.focus).toHaveBeenCalled(); - }); - - it('moves focus to the section group if at the first subsection', function() { - var current = $('a.focusable:contains("Lesson 3 - Be Social")')[0], - destination = $('ol.focusable:contains("Lesson 3 - Be Social")')[0]; - - triggerKeyListener(current, destination, constants.keyCodes.up); - - expect(destination.focus).toHaveBeenCalled(); - }); - - it('moves focus last subsection of the previous section if at a section boundary', function() { - var current = $('li.focusable:contains("Example Week 3: Be Social")')[0], - destination = $('a.focusable:contains("Homework - Essays")')[0]; - - triggerKeyListener(current, destination, constants.keyCodes.up); - - expect(destination.focus).toHaveBeenCalled(); - }); - }); - }); - - describe('eventing', function() { - beforeEach(function() { - loadFixtures('course_experience/fixtures/course-outline-fragment.html'); - CourseOutlineFactory('.block-tree'); - spyOn(Logger, 'log'); - }); - - it('sends an event when an outline section is clicked', function() { - $('a.focusable:contains("Homework - Labs and Demos")').click(); - - expect(Logger.log).toHaveBeenCalledWith('edx.ui.lms.link_clicked', { - target_url: window.location.origin + - '/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type' + - '@sequential+block@graded_simulations', - current_url: window.location.toString() - }); - }); - }); - }); - } -); diff --git a/package.json b/package.json index 0f5fb8b8c7bb..b7ffea7ac6aa 100644 --- a/package.json +++ b/package.json @@ -22,13 +22,9 @@ "devDependencies": { "babel-core": "^6.23.0", "babel-loader": "^6.4.0", - "babel-plugin-react": "^1.0.0", - "babel-plugin-transform-object-rest-spread": "^6.23.0", - "babel-polyfill": "^6.23.0", "babel-preset-env": "^1.2.1", - "babel-preset-react": "^6.23.0", "edx-custom-a11y-rules": "0.1.3", - "eslint-config-edx": "^2.0.0", + "eslint-config-edx": "^2.0.1", "eslint-config-edx-es5": "^2.0.0", "jasmine-core": "^2.4.1", "jasmine-jquery": "^2.1.1", @@ -40,11 +36,13 @@ "karma-jasmine-html-reporter": "^0.2.0", "karma-junit-reporter": "^0.4.1", "karma-requirejs": "^0.2.6", + "karma-sourcemap-loader": "^0.3.7", "karma-spec-reporter": "^0.0.20", + "karma-webpack": "^2.0.3", "pa11y": "4.0.1", "pa11y-reporter-json-oldnode": "1.0.0", "plato": "1.2.2", - "sinon": "1.17.3 || >1.17.4 <2.0.0", + "sinon": "^1.17.7", "squirejs": "^0.1.0", "webpack": "^2.2.1", "webpack-bundle-tracker": "^0.2.0" diff --git a/webpack.config.js b/webpack.config.js index 9a63338265ff..522a4221ab81 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -12,11 +12,13 @@ const wpconfig = { }, output: { - path: path.resolve(__dirname, 'common/static/bundles'), - filename: '[name]-[hash].js', - libraryTarget: 'window', + path: path.resolve(__dirname, 'common/static/bundles'), + filename: '[name]-[hash].js', + libraryTarget: 'window', }, + devtool: isProd ? false : 'cheap-eval-source-map', + plugins: [ new webpack.NoEmitOnErrorsPlugin(), new webpack.NamedModulesPlugin(),