Merge remote-tracking branch 'edx/master' into opaque-keys-merge-master
Conflicts: cms/djangoapps/contentstore/views/item.py cms/djangoapps/contentstore/views/tests/test_container.py cms/djangoapps/contentstore/views/tests/test_tabs.py common/lib/xmodule/xmodule/modulestore/mongo/draft.py lms/djangoapps/certificates/management/commands/gen_cert_report.py lms/djangoapps/certificates/queue.py lms/djangoapps/certificates/views.py lms/djangoapps/courseware/module_render.py lms/djangoapps/courseware/tests/test_module_render.py lms/djangoapps/instructor/views/api.py lms/djangoapps/instructor/views/instructor_dashboard.py lms/djangoapps/instructor/views/legacy.py lms/djangoapps/shoppingcart/tests/test_models.py lms/djangoapps/verify_student/views.py
This commit is contained in:
@@ -76,5 +76,24 @@ define(["jquery", "underscore", "js/views/baseview", "js/utils/handle_iframe_bin
|
||||
expect(view.$('.is-collapsible')).not.toHaveClass('collapsed');
|
||||
});
|
||||
});
|
||||
|
||||
describe("disabled element while running", function() {
|
||||
it("adds 'is-disabled' class to element while action is running and removes it after", function() {
|
||||
var viewWithLink,
|
||||
link,
|
||||
deferred = new $.Deferred(),
|
||||
promise = deferred.promise(),
|
||||
view = new BaseView();
|
||||
|
||||
setFixtures("<a href='#' id='link'>ripe apples drop about my head</a>");
|
||||
|
||||
link = $("#link");
|
||||
expect(link).not.toHaveClass("is-disabled");
|
||||
view.disableElementWhileRunning(link, function(){return promise});
|
||||
expect(link).toHaveClass("is-disabled");
|
||||
deferred.resolve();
|
||||
expect(link).not.toHaveClass("is-disabled");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
215
cms/static/js/spec/views/container_spec.js
Normal file
215
cms/static/js/spec/views/container_spec.js
Normal file
@@ -0,0 +1,215 @@
|
||||
define([ "jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers",
|
||||
"js/views/container", "js/models/xblock_info", "js/views/feedback_notification", "jquery.simulate",
|
||||
"xmodule", "coffee/src/main", "xblock/cms.runtime.v1"],
|
||||
function ($, create_sinon, view_helpers, ContainerView, XBlockInfo, Notification) {
|
||||
|
||||
describe("Container View", function () {
|
||||
|
||||
describe("Supports reordering components", function () {
|
||||
|
||||
var model, containerView, mockContainerHTML, respondWithMockXBlockFragment, init, getComponent,
|
||||
getDragHandle, dragComponentVertically, dragComponentAbove,
|
||||
verifyRequest, verifyNumReorderCalls, respondToRequest,
|
||||
|
||||
rootLocator = 'testCourse/branch/draft/split_test/splitFFF',
|
||||
containerTestUrl = '/xblock/' + rootLocator,
|
||||
|
||||
groupAUrl = "/xblock/locator-group-A",
|
||||
groupA = "locator-group-A",
|
||||
groupAComponent1 = "locator-component-A1",
|
||||
groupAComponent2 = "locator-component-A2",
|
||||
groupAComponent3 = "locator-component-A3",
|
||||
|
||||
groupBUrl = "/xblock/locator-group-B",
|
||||
groupB = "locator-group-B",
|
||||
groupBComponent1 = "locator-component-B1",
|
||||
groupBComponent2 = "locator-component-B2",
|
||||
groupBComponent3 = "locator-component-B3";
|
||||
|
||||
mockContainerHTML = readFixtures('mock/mock-container-xblock.underscore');
|
||||
|
||||
respondWithMockXBlockFragment = function (requests, response) {
|
||||
var requestIndex = requests.length - 1;
|
||||
create_sinon.respondWithJson(requests, response, requestIndex);
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
view_helpers.installViewTemplates();
|
||||
appendSetFixtures('<div class="wrapper-xblock level-page" data-locator="' + rootLocator + '"></div>');
|
||||
model = new XBlockInfo({
|
||||
id: rootLocator,
|
||||
display_name: 'Test AB Test',
|
||||
category: 'split_test'
|
||||
});
|
||||
|
||||
containerView = new ContainerView({
|
||||
model: model,
|
||||
view: 'container_preview',
|
||||
el: $('.wrapper-xblock')
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
containerView.remove();
|
||||
});
|
||||
|
||||
init = function (caller) {
|
||||
var requests = create_sinon.requests(caller);
|
||||
containerView.render();
|
||||
|
||||
respondWithMockXBlockFragment(requests, {
|
||||
html: mockContainerHTML,
|
||||
"resources": []
|
||||
});
|
||||
|
||||
$('body').append(containerView.$el);
|
||||
return requests;
|
||||
};
|
||||
|
||||
getComponent = function(locator) {
|
||||
return containerView.$('[data-locator="' + locator + '"]');
|
||||
};
|
||||
|
||||
getDragHandle = function(locator) {
|
||||
var component = getComponent(locator);
|
||||
return component.prev();
|
||||
};
|
||||
|
||||
dragComponentVertically = function (locator, dy) {
|
||||
var handle = getDragHandle(locator);
|
||||
handle.simulate("drag", {dy: dy});
|
||||
};
|
||||
|
||||
dragComponentAbove = function (sourceLocator, targetLocator) {
|
||||
var targetElement = getComponent(targetLocator),
|
||||
targetTop = targetElement.offset().top + 1,
|
||||
handle = getDragHandle(sourceLocator),
|
||||
handleY = handle.offset().top + (handle.height() / 2),
|
||||
dy = targetTop - handleY;
|
||||
handle.simulate("drag", {dy: dy});
|
||||
};
|
||||
|
||||
verifyRequest = function (requests, reorderCallIndex, expectedURL, expectedChildren) {
|
||||
var actualIndex, request, children, i;
|
||||
// 0th call is the response to the initial render call to get HTML.
|
||||
actualIndex = reorderCallIndex + 1;
|
||||
expect(requests.length).toBeGreaterThan(actualIndex);
|
||||
request = requests[actualIndex];
|
||||
expect(request.url).toEqual(expectedURL);
|
||||
children = (JSON.parse(request.requestBody)).children;
|
||||
expect(children.length).toEqual(expectedChildren.length);
|
||||
for (i = 0; i < children.length; i++) {
|
||||
expect(children[i]).toEqual(expectedChildren[i]);
|
||||
}
|
||||
};
|
||||
|
||||
verifyNumReorderCalls = function (requests, expectedCalls) {
|
||||
// Number of calls will be 1 more than expected because of the initial render call to get HTML.
|
||||
expect(requests.length).toEqual(expectedCalls + 1);
|
||||
};
|
||||
|
||||
respondToRequest = function (requests, reorderCallIndex, status) {
|
||||
var actualIndex;
|
||||
// Number of calls will be 1 more than expected because of the initial render call to get HTML.
|
||||
actualIndex = reorderCallIndex + 1;
|
||||
expect(requests.length).toBeGreaterThan(actualIndex);
|
||||
requests[actualIndex].respond(status);
|
||||
};
|
||||
|
||||
it('does nothing if item not moved far enough', function () {
|
||||
var requests = init(this);
|
||||
// Drag the first component in Group A down very slightly but not enough to move it.
|
||||
dragComponentVertically(groupAComponent1, 5);
|
||||
verifyNumReorderCalls(requests, 0);
|
||||
});
|
||||
|
||||
it('can reorder within a group', function () {
|
||||
var requests = init(this);
|
||||
// Drag the third component in Group A to be the first
|
||||
dragComponentAbove(groupAComponent3, groupAComponent1);
|
||||
respondToRequest(requests, 0, 200);
|
||||
verifyRequest(requests, 0, groupAUrl, [groupAComponent3, groupAComponent1, groupAComponent2]);
|
||||
});
|
||||
|
||||
it('can drag from one group to another', function () {
|
||||
var requests = init(this);
|
||||
// Drag the first component in Group B to the top of group A.
|
||||
dragComponentAbove(groupBComponent1, groupAComponent1);
|
||||
|
||||
// Respond to the two requests: add the component to Group A, then remove it from Group B.
|
||||
respondToRequest(requests, 0, 200);
|
||||
respondToRequest(requests, 1, 200);
|
||||
|
||||
verifyRequest(requests, 0, groupAUrl,
|
||||
[groupBComponent1, groupAComponent1, groupAComponent2, groupAComponent3]);
|
||||
verifyRequest(requests, 1, groupBUrl, [groupBComponent2, groupBComponent3]);
|
||||
});
|
||||
|
||||
it('does not remove from old group if addition to new group fails', function () {
|
||||
var requests = init(this);
|
||||
// Drag the first component in Group B to the first group.
|
||||
dragComponentAbove(groupBComponent1, groupAComponent1);
|
||||
respondToRequest(requests, 0, 500);
|
||||
// Send failure for addition to new group -- no removal event should be received.
|
||||
verifyRequest(requests, 0, groupAUrl,
|
||||
[groupBComponent1, groupAComponent1, groupAComponent2, groupAComponent3]);
|
||||
// Verify that a second request was not issued
|
||||
verifyNumReorderCalls(requests, 1);
|
||||
});
|
||||
|
||||
it('can swap group A and group B', function () {
|
||||
var requests = init(this);
|
||||
// Drag Group B before group A.
|
||||
dragComponentAbove(groupB, groupA);
|
||||
respondToRequest(requests, 0, 200);
|
||||
verifyRequest(requests, 0, containerTestUrl, [groupB, groupA]);
|
||||
});
|
||||
|
||||
describe("Shows a saving message", function () {
|
||||
var savingSpies;
|
||||
|
||||
beforeEach(function () {
|
||||
savingSpies = spyOnConstructor(Notification, "Mini",
|
||||
["show", "hide"]);
|
||||
savingSpies.show.andReturn(savingSpies);
|
||||
});
|
||||
|
||||
it('hides saving message upon success', function () {
|
||||
var requests, savingOptions;
|
||||
requests = init(this);
|
||||
|
||||
// Drag the first component in Group B to the first group.
|
||||
dragComponentAbove(groupBComponent1, groupAComponent1);
|
||||
|
||||
expect(savingSpies.constructor).toHaveBeenCalled();
|
||||
expect(savingSpies.show).toHaveBeenCalled();
|
||||
expect(savingSpies.hide).not.toHaveBeenCalled();
|
||||
savingOptions = savingSpies.constructor.mostRecentCall.args[0];
|
||||
expect(savingOptions.title).toMatch(/Saving/);
|
||||
|
||||
respondToRequest(requests, 0, 200);
|
||||
expect(savingSpies.hide).not.toHaveBeenCalled();
|
||||
respondToRequest(requests, 1, 200);
|
||||
expect(savingSpies.hide).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not hide saving message if failure', function () {
|
||||
var requests = init(this);
|
||||
|
||||
// Drag the first component in Group B to the first group.
|
||||
dragComponentAbove(groupBComponent1, groupAComponent1);
|
||||
|
||||
expect(savingSpies.constructor).toHaveBeenCalled();
|
||||
expect(savingSpies.show).toHaveBeenCalled();
|
||||
expect(savingSpies.hide).not.toHaveBeenCalled();
|
||||
|
||||
respondToRequest(requests, 0, 500);
|
||||
expect(savingSpies.hide).not.toHaveBeenCalled();
|
||||
|
||||
// Since the first reorder call failed, the removal will not be called.
|
||||
verifyNumReorderCalls(requests, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -12,7 +12,7 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
|
||||
|
||||
beforeEach(function () {
|
||||
edit_helpers.installEditTemplates();
|
||||
appendSetFixtures('<div class="xblock" data-locator="mock-xblock" data-display-name="Mock XBlock"></div>');
|
||||
appendSetFixtures('<div class="xblock" data-locator="mock-xblock"></div>');
|
||||
model = new XBlockInfo({
|
||||
id: 'testCourse/branch/draft/block/verticalFFF',
|
||||
display_name: 'Test Unit',
|
||||
|
||||
@@ -162,5 +162,79 @@ define(["coffee/src/views/unit", "js/models/module_info", "js/spec_helpers/creat
|
||||
verifyComponents(unit, ['loc_1', 'loc_2']);
|
||||
});
|
||||
});
|
||||
describe("Disabled edit/publish links during ajax call", function() {
|
||||
var unit,
|
||||
link,
|
||||
draft_states = [
|
||||
{
|
||||
state: "draft",
|
||||
selector: ".publish-draft"
|
||||
},
|
||||
{
|
||||
state: "public",
|
||||
selector: ".create-draft"
|
||||
}
|
||||
],
|
||||
editLinkFixture =
|
||||
'<div class="main-wrapper edit-state-draft" data-locator="unit_locator"> \
|
||||
<div class="unit-settings window"> \
|
||||
<h4 class="header">Unit Settings</h4> \
|
||||
<div class="window-contents"> \
|
||||
<div class="row published-alert"> \
|
||||
<p class="edit-draft-message"> \
|
||||
<a href="#" class="create-draft">edit a draft</a> \
|
||||
</p> \
|
||||
<p class="publish-draft-message"> \
|
||||
<a href="#" class="publish-draft">replace it with this draft</a> \
|
||||
</p> \
|
||||
</div> \
|
||||
</div> \
|
||||
</div> \
|
||||
</div>';
|
||||
function test_link_disabled_during_ajax_call(draft_state) {
|
||||
beforeEach(function () {
|
||||
setFixtures(editLinkFixture);
|
||||
unit = new UnitEditView({
|
||||
el: $('.main-wrapper'),
|
||||
model: new ModuleModel({
|
||||
id: 'unit_locator',
|
||||
state: draft_state['state']
|
||||
})
|
||||
});
|
||||
// needed to stub out the ajax
|
||||
window.analytics = jasmine.createSpyObj('analytics', ['track']);
|
||||
window.course_location_analytics = jasmine.createSpy('course_location_analytics');
|
||||
window.unit_location_analytics = jasmine.createSpy('unit_location_analytics');
|
||||
});
|
||||
|
||||
it("reenables the " + draft_state['selector'] + " link once the ajax call returns", function() {
|
||||
runs(function(){
|
||||
spyOn($, "ajax").andCallThrough();
|
||||
spyOn($.fn, 'addClass').andCallThrough();
|
||||
spyOn($.fn, 'removeClass').andCallThrough();
|
||||
link = $(draft_state['selector']);
|
||||
link.click();
|
||||
});
|
||||
waitsFor(function(){
|
||||
// wait for "is-disabled" to be removed as a class
|
||||
return !($(draft_state['selector']).hasClass("is-disabled"));
|
||||
}, 500);
|
||||
runs(function(){
|
||||
// check that the `is-disabled` class was added and removed
|
||||
expect($.fn.addClass).toHaveBeenCalledWith("is-disabled");
|
||||
expect($.fn.removeClass).toHaveBeenCalledWith("is-disabled");
|
||||
|
||||
// make sure the link finishes without the `is-disabled` class
|
||||
expect(link).not.toHaveClass("is-disabled");
|
||||
|
||||
// affirm that ajax was called
|
||||
expect($.ajax).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
};
|
||||
for (var i = 0; i < draft_states.length; i++) {
|
||||
test_link_disabled_during_ajax_call(draft_states[i]);
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user