Commit 85be9b42 by Peter Fogg Committed by cahrens

Jasmine tests for drag/drop.

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