Commit 85be9b42 by Peter Fogg Committed by cahrens

Jasmine tests for drag/drop.

parent 7640ade4
describe "Course Overview", ->
beforeEach ->
_.each ["/static/js/vendor/date.js", "/static/js/vendor/timepicker/jquery.timepicker.js", "/jsi18n/"], (path) ->
_.each ["/static/js/vendor/date.js", "/static/js/vendor/timepicker/jquery.timepicker.js", "/jsi18n/", "/static/js/vendor/draggabilly.pkgd.js"], (path) ->
appendSetFixtures """
<script type="text/javascript" src="#{path}"></script>
"""
......@@ -45,15 +45,23 @@ describe "Course Overview", ->
</section>
"""
# appendSetFixtures """
# <div class="subsection-list">
# <ol data-id="parent-list-id">
# <li class="unit" data-id="first-unit-id" data-parent-id="parent-list-id"></li>
# <li class="unit" data-id="second-unit-id" data-parent-id="parent-list-id"></li>
# <li class="unit" data-id="third-unit-id" data-parent-id="parent-list-id"></li>
# </ol>
# </div>
# """
appendSetFixtures """
<div class="subsection-list">
<ol class="sortable-unit-list" id="list-1" data-id="parent-list-id-1">
<li class="unit" id="unit-1" data-id="first-unit-id" data-parent-id="parent-list-id-1"></li>
<li class="unit" id="unit-2" data-id="second-unit-id" data-parent-id="parent-list-id-1"></li>
<li class="unit" id="unit-3" data-id="third-unit-id" data-parent-id="parent-list-id-1"></li>
</ol>
</div>
</div class="subsection-list">
<ol class="sortable-unit-list" id="list-2" data-id="parent-list-id-2">
<li class="unit" id="unit-4" data-id="first-unit-id" data-parent-id="parent-list-id-2"></li>
</ol>
</div>
<div class="subsection-list">
<ol class="sortable-unit-list" id="list-3" data-id="parent-list-id-3"></ol>
</div>
"""#"
spyOn(window, 'saveSetSectionScheduleDate').andCallThrough()
# Have to do this here, as it normally gets bound in document.ready()
......@@ -68,6 +76,13 @@ describe "Course Overview", ->
requests = @requests = []
@xhr.onCreate = (req) -> requests.push(req)
CMS.Views.Draggabilly.makeDraggable(
'.unit',
'.unit-drag-handle',
'ol.sortable-unit-list',
'li.branch, article.subsection-body'
)
afterEach ->
delete window.analytics
delete window.course_location_analytics
......@@ -100,3 +115,125 @@ describe "Course Overview", ->
$('a.delete-section-button').click()
$('a.action-primary').click()
expect(@notificationSpy).toHaveBeenCalled()
describe "findDestination", ->
it "correctly finds the drop target of a drag", ->
$ele = $('#unit-1')
$ele.offset(
top: $ele.offset().top + 10, left: $ele.offset().left
)
destination = CMS.Views.Draggabilly.findDestination($ele)
expect(destination.ele).toBe($('#unit-2'))
expect(destination.attachMethod).toBe('before')
it "can drag and drop across section boundaries", ->
$ele = $('#unit-1')
$ele.offset(
top: $('#unit-4').offset().top + 10
left: $ele.offset().left
)
destination = CMS.Views.Draggabilly.findDestination($ele)
expect(destination.ele).toBe($('#unit-4'))
expect(destination.attachMethod).toBe('after')
it "can drag into an empty list", ->
$ele = $('#unit-1')
$ele.offset(
top: $('#list-3').offset().top + 10
left: $ele.offset().left
)
destination = CMS.Views.Draggabilly.findDestination($ele)
expect(destination.ele).toBe($('#list-3'))
expect(destination.attachMethod).toBe('prepend')
it "reports a null destination on a failed drag", ->
$ele = $('#unit-1')
$ele.offset(
top: $ele.offset().top + 200, left: $ele.offset().left
)
destination = CMS.Views.Draggabilly.findDestination($ele)
expect(destination).toEqual(
ele: null
attachMethod: ""
)
describe "onDragStart", ->
it "sets the dragState to its default values", ->
expect(CMS.Views.Draggabilly.dragState).toEqual({})
# Call with some dummy data
CMS.Views.Draggabilly.onDragStart(
{element: $('#unit-1')},
null,
null
)
expect(CMS.Views.Draggabilly.dragState).toEqual(
offset: $('#unit-1').offset()
dropDestination: null,
expandTimer: null,
toExpand: null
)
describe "onDragMove", ->
it "clears the expand timer state", ->
timerSpy = spyOn(window, 'clearTimeout').andCallThrough()
$ele = $('#unit-1')
$ele.offset(
top: $ele.offset().top + 10
left: $ele.offset().left
)
CMS.Views.Draggabilly.onDragMove(
{element: $ele},
null,
null
)
expect(timerSpy).toHaveBeenCalled()
timerSpy.reset()
it "adds the correct CSS class to the drop destination", ->
$ele = $('#unit-1')
$ele.offset(
top: $ele.offset().top + 10, left: $ele.offset().left
)
CMS.Views.Draggabilly.onDragMove(
{element: $ele},
'',
''
)
expect($('#unit-2')).toHaveClass('drop-target drop-target-before')
describe "onDragEnd", ->
beforeEach ->
@reorderSpy = spyOn(CMS.Views.Draggabilly, 'handleReorder')
afterEach ->
@reorderSpy.reset()
it "calls handleReorder on a successful drag", ->
$('#unit-1').offset(
top: $('#unit-1').offset().top + 10
left: $('#unit-1').offset().left
)
CMS.Views.Draggabilly.onDragEnd(
{element: $('#unit-1')},
null,
{x: $('#unit-1').offset().left}
)
expect(@reorderSpy).toHaveBeenCalled()
it "clears out the drag state", ->
CMS.Views.Draggabilly.onDragEnd(
{element: $('#unit-1')},
null,
null
)
expect(CMS.Views.Draggabilly.dragState).toEqual({})
it "sets the element to the correct position", ->
CMS.Views.Draggabilly.onDragEnd(
{element: $('#unit-1')},
null,
null
)
# Chrome sets the CSS to 'auto', but Firefox uses '0px'.
expect(['0px', 'auto']).toContain($('#unit-1').css('top'))
expect(['0px', 'auto']).toContain($('#unit-1').css('left'))
$(document).ready(function() {
var droppableClasses = 'drop-target drop-target-prepend drop-target-before drop-target-after';
CMS.Views.Draggabilly = {
droppableClasses: 'drop-target drop-target-prepend drop-target-before drop-target-after',
/*
* Determine information about where to drop the currently dragged
* element. Returns the element to attach to and the method of
* attachment ('before', 'after', or 'prepend').
*/
var findDestination = function(ele) {
findDestination: function(ele) {
var eleY = ele.offset().top;
var containers = $(ele.data('droppable-class'));
......@@ -55,7 +54,7 @@ $(document).ready(function() {
// element is actually on top of the sibling,
// rather than next to it. This prevents
// saving when expanding/collapsing a list.
if(Math.abs(eleY - siblingY) < $sibling.height() - 1) {
if(Math.abs(eleY - siblingY) < ele.height() - 1) {
return {
ele: $sibling,
attachMethod: siblingY > eleY ? 'before' : 'after'
......@@ -69,15 +68,15 @@ $(document).ready(function() {
return {
ele: null,
attachMethod: ''
};
};
}
},
// Information about the current drag.
var dragState = {};
dragState: {},
var onDragStart = function(draggie, event, pointer) {
onDragStart: function(draggie, event, pointer) {
var ele = $(draggie.element);
dragState = {
this.dragState = {
// Where we started, in case of a failed drag
offset: ele.offset(),
// Which element will be dropped into/onto on success
......@@ -87,42 +86,42 @@ $(document).ready(function() {
// The list which will be expanded on hover
toExpand: null
};
};
},
var onDragMove = function(draggie, event, pointer) {
onDragMove: function(draggie, event, pointer) {
var ele = $(draggie.element);
var destinationInfo = findDestination(ele);
var destinationInfo = this.findDestination(ele);
var destinationEle = destinationInfo.ele;
var parentList = destinationInfo.parentList;
// Clear the timer if we're not hovering over any element
if(!parentList) {
clearTimeout(dragState.expandTimer);
clearTimeout(this.dragState.expandTimer);
}
// If we're hovering over a new element, clear the timer and
// set a new one
else if(!dragState.toExpand || parentList[0] !== dragState.toExpand[0]) {
clearTimeout(dragState.expandTimer);
dragState.expandTimer = setTimeout(function() {
else if(!this.dragState.toExpand || parentList[0] !== this.dragState.toExpand[0]) {
clearTimeout(this.dragState.expandTimer);
this.dragState.expandTimer = setTimeout(function() {
parentList.removeClass('collapsed');
parentList.find('.expand-collapse-icon').removeClass('expand').addClass('collapse');
}, 400);
dragState.toExpand = parentList;
this.dragState.toExpand = parentList;
}
// Clear out the old destination
if(dragState.dropDestination) {
dragState.dropDestination.removeClass(droppableClasses);
if(this.dragState.dropDestination) {
this.dragState.dropDestination.removeClass(this.droppableClasses);
}
// Mark the new destination
if(destinationEle) {
destinationEle.addClass('drop-target drop-target-' + destinationInfo.attachMethod);
dragState.dropDestination = destinationEle;
this.dragState.dropDestination = destinationEle;
}
};
},
var onDragEnd = function(draggie, event, pointer) {
onDragEnd: function(draggie, event, pointer) {
var ele = $(draggie.element);
var destinationInfo = findDestination(ele);
var destinationInfo = this.findDestination(ele);
var destination = destinationInfo.ele;
// If the drag succeeded, rearrange the DOM and send the result.
......@@ -134,7 +133,7 @@ $(document).ready(function() {
}
var method = destinationInfo.attachMethod;
destination[method](ele);
handleReorder(ele);
this.handleReorder(ele);
}
// Everything in its right place
......@@ -144,17 +143,17 @@ $(document).ready(function() {
});
// Clear dragging state in preparation for the next event.
if(dragState.dropDestination) {
dragState.dropDestination.removeClass(droppableClasses);
if(this.dragState.dropDestination) {
this.dragState.dropDestination.removeClass(this.droppableClasses);
}
clearTimeout(dragState.expandTimer);
dragState = {};
};
clearTimeout(this.dragState.expandTimer);
this.dragState = {};
},
/*
* Find all parent-child changes and save them.
*/
var handleReorder = function(ele) {
handleReorder: function(ele) {
var parentSelector = ele.data('parent-location-selector');
var childrenSelector = ele.data('child-selector');
var newParentEle = ele.parents(parentSelector).first();
......@@ -166,7 +165,7 @@ $(document).ready(function() {
var oldParentEle = $(parentSelector).filter(function() {
return $(this).data('id') === oldParentID;
});
saveItem(oldParentEle, childrenSelector, function() {
this.saveItem(oldParentEle, childrenSelector, function() {
ele.data('parent-id', newParentID);
});
}
......@@ -174,17 +173,17 @@ $(document).ready(function() {
title: gettext('Saving&hellip;')
});
saving.show();
saveItem(newParentEle, childrenSelector, function() {
this.saveItem(newParentEle, childrenSelector, function() {
saving.hide();
});
};
},
/*
* Actually save the update to the server. Takes the element
* representing the parent item to save, a CSS selector to find
* its children, and a success callback.
*/
var saveItem = function(ele, childrenSelector, success) {
saveItem: function(ele, childrenSelector, success) {
// Find all current child IDs.
var children = _.map(
ele.find(childrenSelector),
......@@ -203,14 +202,14 @@ $(document).ready(function() {
}),
success: success
});
};
},
/*
* Make `type` draggable using `handleClass`, able to be dropped
* into `droppableClass`, and with parent type
* `parentLocationSelector`.
*/
var makeDraggable = function(type, handleClass, droppableClass, parentLocationSelector) {
makeDraggable: function(type, handleClass, droppableClass, parentLocationSelector) {
_.each(
$(type),
function(ele) {
......@@ -222,29 +221,31 @@ $(document).ready(function() {
handle: handleClass,
axis: 'y'
});
draggable.on('dragStart', onDragStart);
draggable.on('dragMove', onDragMove);
draggable.on('dragEnd', onDragEnd);
draggable.on('dragStart', _.bind(CMS.Views.Draggabilly.onDragStart, CMS.Views.Draggabilly));
draggable.on('dragMove', _.bind(CMS.Views.Draggabilly.onDragMove, CMS.Views.Draggabilly));
draggable.on('dragEnd', _.bind(CMS.Views.Draggabilly.onDragEnd, CMS.Views.Draggabilly));
}
);
};
}
};
$(document).ready(function() {
// Section
makeDraggable(
CMS.Views.Draggabilly.makeDraggable(
'.courseware-section',
'.section-drag-handle',
'.courseware-overview',
'article.courseware-overview'
);
// Subsection
makeDraggable(
CMS.Views.Draggabilly.makeDraggable(
'.id-holder',
'.subsection-drag-handle',
'.subsection-list > ol',
'.courseware-section'
);
// Unit
makeDraggable(
CMS.Views.Draggabilly.makeDraggable(
'.unit',
'.unit-drag-handle',
'ol.sortable-unit-list',
......
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