Commit 4f0aee3f by Brian Jacobel

Fix unit and acceptance tests broken in upgrade (squashed)

Fix syntax error in selectors

.attr() now returns a string (though it can still be passed an integer)

Fixes checkbox test failures

Remove remaining references to jquery.min (in wrong folder)

$.ajax now returns 422 if type is json and body is not JSON, e.g. ''

Substitute prop for attr

Remove references to jquery.min, add jquery.migrate (again)

"Fix" jquery karma config

This wasn't suppoed to survive the merge

This throws an error when called with an 'undefined' error

Fix Karma warning about [re|un]loading the window

Fix path for jquery in cms-squire tests

Move jasmine.clock.uninstall() to afterEach so it runs even on failure

Fix test failing due to timezone issues

Do the timeout before the window scrolling (so handler will not be _.throttled)

Fix an alert() triggered by window.onBeforeUnload while testing in Chrome
parent a5ef6149
...@@ -33,7 +33,7 @@ require ["jquery", "backbone", "coffee/src/main", "common/js/spec_helpers/ajax_h ...@@ -33,7 +33,7 @@ require ["jquery", "backbone", "coffee/src/main", "common/js/spec_helpers/ajax_h
server && server.restore() server && server.restore()
it "successful AJAX request does not pop an error notification", -> it "successful AJAX request does not pop an error notification", ->
server = AjaxHelpers.server([200, {}, '']) server = AjaxHelpers.server([200, {"Content-Type": "application/json"}, "{}"])
expect($("#page-notification")).toBeEmpty() expect($("#page-notification")).toBeEmpty()
$.ajax("/test") $.ajax("/test")
...@@ -42,7 +42,7 @@ require ["jquery", "backbone", "coffee/src/main", "common/js/spec_helpers/ajax_h ...@@ -42,7 +42,7 @@ require ["jquery", "backbone", "coffee/src/main", "common/js/spec_helpers/ajax_h
expect($("#page-notification")).toBeEmpty() expect($("#page-notification")).toBeEmpty()
it "AJAX request with error should pop an error notification", -> it "AJAX request with error should pop an error notification", ->
server = AjaxHelpers.server([500, {}, '']) server = AjaxHelpers.server([500, {"Content-Type": "application/json"}, "{}"])
$.ajax("/test") $.ajax("/test")
server.respond() server.respond()
...@@ -50,7 +50,7 @@ require ["jquery", "backbone", "coffee/src/main", "common/js/spec_helpers/ajax_h ...@@ -50,7 +50,7 @@ require ["jquery", "backbone", "coffee/src/main", "common/js/spec_helpers/ajax_h
expect($("#page-notification")).toContainElement('div.wrapper-notification-error') expect($("#page-notification")).toContainElement('div.wrapper-notification-error')
it "can override AJAX request with error so it does not pop an error notification", -> it "can override AJAX request with error so it does not pop an error notification", ->
server = AjaxHelpers.server([500, {}, '']) server = AjaxHelpers.server([500, {"Content-Type": "application/json"}, "{}"])
$.ajax $.ajax
url: "/test" url: "/test"
......
...@@ -34,7 +34,7 @@ define ["js/models/section", "common/js/spec_helpers/ajax_helpers", "js/utils/mo ...@@ -34,7 +34,7 @@ define ["js/models/section", "common/js/spec_helpers/ajax_helpers", "js/utils/mo
}) })
it "show/hide a notification when it saves to the server", -> it "show/hide a notification when it saves to the server", ->
server = AjaxHelpers.server([200, {}, '']) server = AjaxHelpers.server([200, {"Content-Type": "application/json"}, "{}"])
@model.save() @model.save()
expect(Section.prototype.showNotification).toHaveBeenCalled() expect(Section.prototype.showNotification).toHaveBeenCalled()
...@@ -43,7 +43,7 @@ define ["js/models/section", "common/js/spec_helpers/ajax_helpers", "js/utils/mo ...@@ -43,7 +43,7 @@ define ["js/models/section", "common/js/spec_helpers/ajax_helpers", "js/utils/mo
it "don't hide notification when saving fails", -> it "don't hide notification when saving fails", ->
# this is handled by the global AJAX error handler # this is handled by the global AJAX error handler
server = AjaxHelpers.server([500, {}, '']) server = AjaxHelpers.server([500, {"Content-Type": "application/json"}, "{}"])
@model.save() @model.save()
server.respond() server.respond()
......
...@@ -7,8 +7,7 @@ define([ // jshint ignore:line ...@@ -7,8 +7,7 @@ define([ // jshint ignore:line
'js/certificates/views/certificate_preview', 'js/certificates/views/certificate_preview',
'common/js/spec_helpers/template_helpers', 'common/js/spec_helpers/template_helpers',
'common/js/spec_helpers/view_helpers', 'common/js/spec_helpers/view_helpers',
'common/js/spec_helpers/ajax_helpers', 'common/js/spec_helpers/ajax_helpers'
'jasmine-stealth'
], ],
function(_, $, Course, CertificatePreview, TemplateHelpers, ViewHelpers, AjaxHelpers) { function(_, $, Course, CertificatePreview, TemplateHelpers, ViewHelpers, AjaxHelpers) {
'use strict'; 'use strict';
......
...@@ -144,7 +144,7 @@ define([ ...@@ -144,7 +144,7 @@ define([
// select the entrance-exam-enabled checkbox. grade requirement section should be visible. // select the entrance-exam-enabled checkbox. grade requirement section should be visible.
entrance_exam_enabled_field entrance_exam_enabled_field
.attr('checked', 'true') .prop('checked', true)
.trigger('change'); .trigger('change');
this.view.render(); this.view.render();
...@@ -152,7 +152,7 @@ define([ ...@@ -152,7 +152,7 @@ define([
// deselect the entrance-exam-enabled checkbox. grade requirement section should be hidden. // deselect the entrance-exam-enabled checkbox. grade requirement section should be hidden.
entrance_exam_enabled_field entrance_exam_enabled_field
.removeAttr('checked') .prop('checked', false)
.trigger('change'); .trigger('change');
expect(this.view.$(SELECTORS.grade_requirement_div)).toBeHidden(); expect(this.view.$(SELECTORS.grade_requirement_div)).toBeHidden();
...@@ -173,7 +173,7 @@ define([ ...@@ -173,7 +173,7 @@ define([
// select the entrance-exam-enabled checkbox. // select the entrance-exam-enabled checkbox.
entrance_exam_enabled_field entrance_exam_enabled_field
.attr('checked', 'true') .prop('checked', true)
.trigger('change'); .trigger('change');
// input a valid value for entrance exam minimum score. // input a valid value for entrance exam minimum score.
......
...@@ -13,6 +13,11 @@ var options = { ...@@ -13,6 +13,11 @@ var options = {
libraryFiles: [], libraryFiles: [],
libraryFilesToInclude: [
{pattern: 'common/js/vendor/jquery.js', included: true},
{pattern: 'common/js/vendor/jquery-migrate.js', included: true}
],
// Make sure the patterns in sourceFiles and specFiles do not match the same file. // 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. // Otherwise Istanbul which is used for coverage tracking will cause tests to not run.
sourceFiles: [ sourceFiles: [
......
...@@ -13,6 +13,11 @@ var options = { ...@@ -13,6 +13,11 @@ var options = {
libraryFiles: [], libraryFiles: [],
libraryFilesToInclude: [
{pattern: 'xmodule_js/common_static/common/js/vendor/jquery.js', included: true},
{pattern: 'xmodule_js/common_static/common/js/vendor/jquery-migrate.js', included: true}
],
// Make sure the patterns in sourceFiles and specFiles do not match the same file. // 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. // Otherwise Istanbul which is used for coverage tracking will cause tests to not run.
sourceFiles: [ sourceFiles: [
......
...@@ -93,12 +93,12 @@ def render_require_js_path_overrides(path_overrides): # pylint: disable=invalid ...@@ -93,12 +93,12 @@ def render_require_js_path_overrides(path_overrides): # pylint: disable=invalid
For example: For example:
"js/vendor/jquery.min.js" --> "js/vendor/jquery.min.abcd1234" "js/vendor/jquery.js" --> "js/vendor/jquery.abcd1234"
To achive this we will add overrided paths in requirejs config at runtime. To achive this we will add overrided paths in requirejs config at runtime.
So that any reference to 'jquery' in a JavaScript module So that any reference to 'jquery' in a JavaScript module
will cause RequireJS to load '/static/js/vendor/jquery.min.abcd1234.js' will cause RequireJS to load '/static/js/vendor/jquery.abcd1234.js'
If running in DEBUG mode (as in devstack), the resolved JavaScript URLs If running in DEBUG mode (as in devstack), the resolved JavaScript URLs
won't contain hashes, so the new paths will match the original paths. won't contain hashes, so the new paths will match the original paths.
......
...@@ -28,7 +28,8 @@ var options = { ...@@ -28,7 +28,8 @@ var options = {
{pattern: 'common_static/edx-ui-toolkit/js/utils/global-loader.js', included: true}, {pattern: 'common_static/edx-ui-toolkit/js/utils/global-loader.js', included: true},
{pattern: 'common_static/js/vendor/CodeMirror/codemirror.js', included: true}, {pattern: 'common_static/js/vendor/CodeMirror/codemirror.js', included: true},
{pattern: 'common_static/js/vendor/draggabilly.js'}, {pattern: 'common_static/js/vendor/draggabilly.js'},
{pattern: 'common_static/js/vendor/jquery.min.js', included: true}, {pattern: 'common_static/common/js/vendor/jquery.js', included: true},
{pattern: 'common_static/common/js/vendor/jquery-migrate.js', included: true},
{pattern: 'common_static/js/vendor/jquery.cookie.js', included: true}, {pattern: 'common_static/js/vendor/jquery.cookie.js', included: true},
{pattern: 'common_static/js/vendor/jquery.leanModal.js', included: true}, {pattern: 'common_static/js/vendor/jquery.leanModal.js', included: true},
{pattern: 'common_static/js/vendor/jquery.timeago.js', included: true}, {pattern: 'common_static/js/vendor/jquery.timeago.js', included: true},
......
...@@ -289,9 +289,9 @@ describe 'Problem', -> ...@@ -289,9 +289,9 @@ describe 'Problem', ->
$('#input_example_1').replaceWith(html) $('#input_example_1').replaceWith(html)
@problem.checkAnswersAndCheckButton true @problem.checkAnswersAndCheckButton true
@checkDisabled true @checkDisabled true
$('#input_1_1_1').attr('checked', true).trigger('click') $('#input_1_1_1').click()
@checkDisabled false @checkDisabled false
$('#input_1_1_1').attr('checked', false).trigger('click') $('#input_1_1_1').click()
@checkDisabled true @checkDisabled true
it 'should become enabled after a radiobutton is checked', -> it 'should become enabled after a radiobutton is checked', ->
......
...@@ -343,7 +343,7 @@ ...@@ -343,7 +343,7 @@
expect(parseIntAttribute(item, 'data-index')).toEqual(index); expect(parseIntAttribute(item, 'data-index')).toEqual(index);
expect(parseIntAttribute(item, 'data-start')).toEqual(captionsData.start[index]); expect(parseIntAttribute(item, 'data-start')).toEqual(captionsData.start[index]);
expect(item.attr('tabindex')).toEqual(0); expect(item.attr('tabindex')).toEqual('0');
expect(item.text().trim()).toEqual(captionsData.text[index]); expect(item.text().trim()).toEqual(captionsData.text[index]);
}); });
}); });
...@@ -432,7 +432,7 @@ ...@@ -432,7 +432,7 @@
expect(parseIntAttribute(item, 'data-index')).toEqual(index); expect(parseIntAttribute(item, 'data-index')).toEqual(index);
expect(parseIntAttribute(item, 'data-start')).toEqual(captionsData.start[index]); expect(parseIntAttribute(item, 'data-start')).toEqual(captionsData.start[index]);
expect(item.attr('tabindex')).toEqual(0); expect(item.attr('tabindex')).toEqual('0');
expect(item.text().trim()).toEqual(text); expect(item.text().trim()).toEqual(text);
}); });
}).always(done); }).always(done);
...@@ -842,7 +842,7 @@ ...@@ -842,7 +842,7 @@
function (index, item) { function (index, item) {
expect(parseIntAttribute($(item), 'data-index')).toEqual(index); expect(parseIntAttribute($(item), 'data-index')).toEqual(index);
expect(parseIntAttribute($(item), 'data-start')).toEqual(captionsData.start[index]); expect(parseIntAttribute($(item), 'data-start')).toEqual(captionsData.start[index]);
expect($(item).attr('tabindex')).toEqual(0); expect($(item).attr('tabindex')).toEqual('0');
expect($(item).text().trim()).toEqual(captionsData.text[index]); expect($(item).text().trim()).toEqual(captionsData.text[index]);
}); });
}); });
......
...@@ -49,8 +49,8 @@ ...@@ -49,8 +49,8 @@
}); });
it('from the start, focus grabbers are disabled', function () { it('from the start, focus grabbers are disabled', function () {
expect(state.focusGrabber.elFirst.attr('tabindex')).toBe(-1); expect(state.focusGrabber.elFirst.attr('tabindex')).toBe('-1');
expect(state.focusGrabber.elLast.attr('tabindex')).toBe(-1); expect(state.focusGrabber.elLast.attr('tabindex')).toBe('-1');
}); });
it( it(
......
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
expect(btnPlay).not.toHaveClass('is-hidden'); expect(btnPlay).not.toHaveClass('is-hidden');
expect(btnPlay).toHaveAttrs({ expect(btnPlay).toHaveAttrs({
'aria-hidden': 'false', 'aria-hidden': 'false',
'tabindex': 0 'tabindex': '0'
}); });
state.videoPlayPlaceholder.hide(); state.videoPlayPlaceholder.hide();
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
expect(btnPlay).toHaveClass('is-hidden'); expect(btnPlay).toHaveClass('is-hidden');
expect(btnPlay).toHaveAttrs({ expect(btnPlay).toHaveAttrs({
'aria-hidden': 'true', 'aria-hidden': 'true',
'tabindex': -1 'tabindex': '-1'
}); });
}); });
......
...@@ -19,7 +19,8 @@ var options = { ...@@ -19,7 +19,8 @@ var options = {
libraryFilesToInclude: [ libraryFilesToInclude: [
{pattern: 'coffee/src/ajax_prefix.js', included: true}, {pattern: 'coffee/src/ajax_prefix.js', included: true},
{pattern: 'js/vendor/draggabilly.js', included: true}, {pattern: 'js/vendor/draggabilly.js', included: true},
{pattern: 'js/vendor/jquery.min.js', included: true}, {pattern: 'common/js/vendor/jquery.js', included: true},
{pattern: 'common/js/vendor/jquery-migrate.js', included: true},
{pattern: 'coffee/src/jquery.immediateDescendents.js', included: true}, {pattern: 'coffee/src/jquery.immediateDescendents.js', included: true},
{pattern: 'js/vendor/jquery.leanModal.js', included: true}, {pattern: 'js/vendor/jquery.leanModal.js', included: true},
{pattern: 'js/vendor/jquery.timeago.js', included: true}, {pattern: 'js/vendor/jquery.timeago.js', included: true},
......
...@@ -79,11 +79,11 @@ class DataDownload ...@@ -79,11 +79,11 @@ class DataDownload
@$list_may_enroll_csv_btn = @$section.find("input[name='list-may-enroll-csv']") @$list_may_enroll_csv_btn = @$section.find("input[name='list-may-enroll-csv']")
@$list_problem_responses_csv_input = @$section.find("input[name='problem-location']") @$list_problem_responses_csv_input = @$section.find("input[name='problem-location']")
@$list_problem_responses_csv_btn = @$section.find("input[name='list-problem-responses-csv']") @$list_problem_responses_csv_btn = @$section.find("input[name='list-problem-responses-csv']")
@$list_anon_btn = @$section.find("input[name='list-anon-ids']'") @$list_anon_btn = @$section.find("input[name='list-anon-ids']")
@$grade_config_btn = @$section.find("input[name='dump-gradeconf']'") @$grade_config_btn = @$section.find("input[name='dump-gradeconf']")
@$calculate_grades_csv_btn = @$section.find("input[name='calculate-grades-csv']'") @$calculate_grades_csv_btn = @$section.find("input[name='calculate-grades-csv']")
@$problem_grade_report_csv_btn = @$section.find("input[name='problem-grade-report']'") @$problem_grade_report_csv_btn = @$section.find("input[name='problem-grade-report']")
@$async_report_btn = @$section.find("input[class='async-report-btn']'") @$async_report_btn = @$section.find("input[class='async-report-btn']")
# response areas # response areas
@$download = @$section.find '.data-download-container' @$download = @$section.find '.data-download-container'
......
define(['jquery', 'logger', 'js/courseware/toggle_element_visibility'], define(['jquery', 'logger', 'js/courseware/toggle_element_visibility', 'moment'],
function ($, Logger, ToggleElementVisibility) { function ($, Logger, ToggleElementVisibility, moment) {
'use strict'; 'use strict';
describe('show/hide with mouse click', function () { describe('show/hide with mouse click', function () {
...@@ -43,7 +43,7 @@ define(['jquery', 'logger', 'js/courseware/toggle_element_visibility'], ...@@ -43,7 +43,7 @@ define(['jquery', 'logger', 'js/courseware/toggle_element_visibility'],
$update.siblings('.toggle-visibility-button').trigger('click'); $update.siblings('.toggle-visibility-button').trigger('click');
expect(Logger.log).toHaveBeenCalledWith('edx.course.home.course_update.toggled', { expect(Logger.log).toHaveBeenCalledWith('edx.course.home.course_update.toggled', {
action: 'hide', action: 'hide',
publish_date: '2015-12-01T00:00:00+00:00' publish_date: moment('December 1, 2015', 'MMM DD, YYYY').format()
}); });
}); });
}); });
......
...@@ -107,6 +107,12 @@ define([ ...@@ -107,6 +107,12 @@ define([
'templates/discovery/filter_bar' 'templates/discovery/filter_bar'
]); ]);
DiscoveryFactory(MEANINGS); DiscoveryFactory(MEANINGS);
jasmine.clock().install();
});
afterEach(function () {
jasmine.clock().uninstall();
}); });
it('does search', function () { it('does search', function () {
...@@ -121,22 +127,20 @@ define([ ...@@ -121,22 +127,20 @@ define([
it('loads more', function () { it('loads more', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
jasmine.clock().install();
$('.discovery-input').val('test'); $('.discovery-input').val('test');
$('.discovery-submit').trigger('click'); $('.discovery-submit').trigger('click');
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE); AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
expect($('.courses-listing article').length).toEqual(1); expect($('.courses-listing article').length).toEqual(1);
expect($('.courses-listing .course-title')).toContainHtml('edX Demonstration Course'); expect($('.courses-listing .course-title')).toContainHtml('edX Demonstration Course');
jasmine.clock().tick(500);
window.scroll(0, $(document).height()); window.scroll(0, $(document).height());
$(window).trigger('scroll'); $(window).trigger('scroll');
jasmine.clock().tick(500);
// TODO: determine why the search API is invoked twice // TODO: determine why the search API is invoked twice
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE); AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE); AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
expect($('.courses-listing article').length).toEqual(2); expect($('.courses-listing article').length).toEqual(2);
jasmine.clock().uninstall();
}); });
it('displays not found message', function () { it('displays not found message', function () {
......
...@@ -54,6 +54,11 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -54,6 +54,11 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
TemplateHelpers.installTemplate("templates/fields/message_banner"); TemplateHelpers.installTemplate("templates/fields/message_banner");
}); });
afterEach(function () {
// image_field.js's window.onBeforeUnload breaks Karma in Chrome, clean it up after each test
$(window).off('beforeunload');
});
var createFakeImageFile = function (size) { var createFakeImageFile = function (size) {
var fileFakeData = 'i63ljc6giwoskyb9x5sw0169bdcmcxr3cdz8boqv0lik971972cmd6yknvcxr5sw0nvc169bdcmcxsdf'; var fileFakeData = 'i63ljc6giwoskyb9x5sw0169bdcmcxr3cdz8boqv0lik971972cmd6yknvcxr5sw0nvc169bdcmcxsdf';
return new Blob( return new Blob(
...@@ -260,6 +265,7 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -260,6 +265,7 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
// Verify image upload progress message // Verify image upload progress message
verifyImageUploadButtonMessage(imageView, true); verifyImageUploadButtonMessage(imageView, true);
window.onbeforeunload = null;
$(window).trigger('beforeunload'); $(window).trigger('beforeunload');
expect(imageView.onBeforeUnload).toHaveBeenCalled(); expect(imageView.onBeforeUnload).toHaveBeenCalled();
}); });
......
...@@ -78,7 +78,8 @@ ...@@ -78,7 +78,8 @@
$(this.el).show(); // Show in case the form was hidden for auto-submission $(this.el).show(); // Show in case the form was hidden for auto-submission
this.errors = _.flatten( this.errors = _.flatten(
_.map( _.map(
JSON.parse(error.responseText), // Something is passing this 'undefined'. Protect against this.
JSON.parse(error.responseText || "[]"),
function(error_list) { function(error_list) {
return _.map( return _.map(
error_list, error_list,
......
...@@ -13,6 +13,8 @@ var options = { ...@@ -13,6 +13,8 @@ var options = {
// Avoid adding files to this list. Use RequireJS. // Avoid adding files to this list. Use RequireJS.
libraryFilesToInclude: [ libraryFilesToInclude: [
{pattern: 'common/js/vendor/jquery.js', included: true},
{pattern: 'common/js/vendor/jquery-migrate.js', included: true},
{pattern: 'xmodule_js/common_static/js/vendor/jquery.event.drag-2.2.js', included: true}, {pattern: 'xmodule_js/common_static/js/vendor/jquery.event.drag-2.2.js', included: true},
{pattern: 'xmodule_js/common_static/js/vendor/slick.core.js', included: true}, {pattern: 'xmodule_js/common_static/js/vendor/slick.core.js', included: true},
{pattern: 'xmodule_js/common_static/js/vendor/slick.grid.js', included: true} {pattern: 'xmodule_js/common_static/js/vendor/slick.grid.js', included: true}
......
...@@ -25,7 +25,8 @@ var options = { ...@@ -25,7 +25,8 @@ var options = {
{pattern: 'xmodule_js/common_static/js/src/logger.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/test/i18n.js', included: true},
{pattern: 'xmodule_js/common_static/js/vendor/CodeMirror/codemirror.js', included: true}, {pattern: 'xmodule_js/common_static/js/vendor/CodeMirror/codemirror.js', included: true},
{pattern: 'xmodule_js/common_static/js/vendor/jquery.min.js', included: true}, {pattern: 'common/js/vendor/jquery.js', included: true},
{pattern: 'common/js/vendor/jquery-migrate.js', included: true},
{pattern: 'xmodule_js/common_static/js/vendor/jquery.cookie.js', included: true}, {pattern: 'xmodule_js/common_static/js/vendor/jquery.cookie.js', included: true},
{pattern: 'xmodule_js/common_static/js/vendor/flot/jquery.flot.js', included: true}, {pattern: 'xmodule_js/common_static/js/vendor/flot/jquery.flot.js', included: true},
{pattern: 'xmodule_js/common_static/coffee/src/jquery.immediateDescendents.js', included: true}, {pattern: 'xmodule_js/common_static/coffee/src/jquery.immediateDescendents.js', included: true},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment