Address PR comments.
Remove unused variables; some changes to match existing BDD spec for drag/drop (expand-on-hover timeout and dropping with mouse outside of element); bugfix for section expand; some code cleanup; update and re-enable acceptance test.
This commit is contained in:
@@ -128,10 +128,10 @@ def change_grading_status(step):
|
||||
|
||||
@step(u'I reorder subsections')
|
||||
def reorder_subsections(_step):
|
||||
draggable_css = 'a.drag-handle'
|
||||
draggable_css = '.subsection-drag-handle'
|
||||
ele = world.css_find(draggable_css).first
|
||||
ele.action_chains.drag_and_drop_by_offset(
|
||||
ele._element,
|
||||
30,
|
||||
0
|
||||
0,
|
||||
10
|
||||
).perform()
|
||||
|
||||
102
cms/static/coffee/spec/views/overview_spec.coffee
Normal file
102
cms/static/coffee/spec/views/overview_spec.coffee
Normal file
@@ -0,0 +1,102 @@
|
||||
describe "Course Overview", ->
|
||||
|
||||
beforeEach ->
|
||||
_.each ["/static/js/vendor/date.js", "/static/js/vendor/timepicker/jquery.timepicker.js", "/jsi18n/"], (path) ->
|
||||
appendSetFixtures """
|
||||
<script type="text/javascript" src="#{path}"></script>
|
||||
"""
|
||||
|
||||
appendSetFixtures """
|
||||
<div class="section-published-date">
|
||||
<span class="published-status">
|
||||
<strong>Will Release:</strong> 06/12/2013 at 04:00 UTC
|
||||
</span>
|
||||
<a href="#" class="edit-button" data-date="06/12/2013" data-time="04:00" data-id="i4x://pfogg/42/chapter/d6b47f7b084f49debcaf67fe5436c8e2">Edit</a>
|
||||
</div>
|
||||
"""
|
||||
|
||||
appendSetFixtures """
|
||||
<div class="edit-subsection-publish-settings">
|
||||
<div class="settings">
|
||||
<h3>Section Release Date</h3>
|
||||
<div class="picker datepair">
|
||||
<div class="field field-start-date">
|
||||
<label for="">Release Day</label>
|
||||
<input class="start-date date" type="text" name="start_date" value="04/08/1990" placeholder="MM/DD/YYYY" class="date" size='15' autocomplete="off"/>
|
||||
</div>
|
||||
<div class="field field-start-time">
|
||||
<label for="">Release Time (<abbr title="Coordinated Universal Time">UTC</abbr>)</label>
|
||||
<input class="start-time time" type="text" name="start_time" value="12:00" placeholder="HH:MM" class="time" size='10' autocomplete="off"/>
|
||||
</div>
|
||||
<div class="description">
|
||||
<p>On the date set above, this section – <strong class="section-name"></strong> – will be released to students. Any units marked private will only be visible to admins.</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" class="save-button">Save</a><a href="#" class="cancel-button">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
appendSetFixtures """
|
||||
<section class="courseware-section branch" data-id="a-location-goes-here">
|
||||
<li class="branch collapsed id-holder" data-id="an-id-goes-here">
|
||||
<a href="#" class="delete-section-button"></a>
|
||||
</li>
|
||||
</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>
|
||||
# """
|
||||
|
||||
spyOn(window, 'saveSetSectionScheduleDate').andCallThrough()
|
||||
# Have to do this here, as it normally gets bound in document.ready()
|
||||
$('a.save-button').click(saveSetSectionScheduleDate)
|
||||
$('a.delete-section-button').click(deleteSection)
|
||||
$(".edit-subsection-publish-settings .start-date").datepicker()
|
||||
|
||||
@notificationSpy = spyOn(CMS.Views.Notification.Mini.prototype, 'show').andCallThrough()
|
||||
window.analytics = jasmine.createSpyObj('analytics', ['track'])
|
||||
window.course_location_analytics = jasmine.createSpy()
|
||||
@xhr = sinon.useFakeXMLHttpRequest()
|
||||
requests = @requests = []
|
||||
@xhr.onCreate = (req) -> requests.push(req)
|
||||
|
||||
afterEach ->
|
||||
delete window.analytics
|
||||
delete window.course_location_analytics
|
||||
@notificationSpy.reset()
|
||||
|
||||
it "should save model when save is clicked", ->
|
||||
$('a.edit-button').click()
|
||||
$('a.save-button').click()
|
||||
expect(saveSetSectionScheduleDate).toHaveBeenCalled()
|
||||
|
||||
it "should show a confirmation on save", ->
|
||||
$('a.edit-button').click()
|
||||
$('a.save-button').click()
|
||||
expect(@notificationSpy).toHaveBeenCalled()
|
||||
|
||||
it "should delete model when delete is clicked", ->
|
||||
deleteSpy = spyOn(window, '_deleteItem').andCallThrough()
|
||||
$('a.delete-section-button').click()
|
||||
$('a.action-primary').click()
|
||||
expect(deleteSpy).toHaveBeenCalled()
|
||||
expect(@requests[0].url).toEqual('/delete_item')
|
||||
|
||||
it "should not delete model when cancel is clicked", ->
|
||||
deleteSpy = spyOn(window, '_deleteItem').andCallThrough()
|
||||
$('a.delete-section-button').click()
|
||||
$('a.action-secondary').click()
|
||||
expect(@requests.length).toEqual(0)
|
||||
|
||||
it "should show a confirmation on delete", ->
|
||||
$('a.delete-section-button').click()
|
||||
$('a.action-primary').click()
|
||||
expect(@notificationSpy).toHaveBeenCalled()
|
||||
@@ -1,57 +1,13 @@
|
||||
$(document).ready(function() {
|
||||
|
||||
// Section
|
||||
makeDraggable(
|
||||
'.courseware-section',
|
||||
'.section-drag-handle',
|
||||
'.courseware-overview',
|
||||
'article.courseware-overview'
|
||||
);
|
||||
// Subsection
|
||||
makeDraggable(
|
||||
'.id-holder',
|
||||
'.subsection-drag-handle',
|
||||
'.subsection-list > ol',
|
||||
'.courseware-section'
|
||||
);
|
||||
// Unit
|
||||
makeDraggable(
|
||||
'.unit',
|
||||
'.unit-drag-handle',
|
||||
'ol.sortable-unit-list',
|
||||
'li.branch, article.subsection-body'
|
||||
);
|
||||
|
||||
/*
|
||||
* Make `type` draggable using `handleClass`, able to be dropped
|
||||
* into `droppableClass`, and with parent type
|
||||
* `parentLocationSelector`.
|
||||
*/
|
||||
function makeDraggable(type, handleClass, droppableClass, parentLocationSelector) {
|
||||
_.each(
|
||||
$(type),
|
||||
function(ele) {
|
||||
// Remember data necessary to reconstruct the parent-child relationships
|
||||
$(ele).data('droppable-class', droppableClass);
|
||||
$(ele).data('parent-location-selector', parentLocationSelector);
|
||||
$(ele).data('child-selector', type);
|
||||
var draggable = new Draggabilly(ele, {
|
||||
handle: handleClass,
|
||||
axis: 'y'
|
||||
});
|
||||
draggable.on('dragStart', onDragStart);
|
||||
draggable.on('dragMove', onDragMove);
|
||||
draggable.on('dragEnd', onDragEnd);
|
||||
}
|
||||
);
|
||||
}
|
||||
var 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').
|
||||
*/
|
||||
function findDestination(ele) {
|
||||
var findDestination = function(ele) {
|
||||
var eleY = ele.offset().top;
|
||||
var containers = $(ele.data('droppable-class'));
|
||||
|
||||
@@ -90,7 +46,11 @@ $(document).ready(function() {
|
||||
for(var j = 0; j < siblings.length; j++) {
|
||||
var $sibling = $(siblings[j]);
|
||||
var siblingY = $sibling.offset().top;
|
||||
if(Math.abs(eleY - siblingY) < $sibling.height()) {
|
||||
// Subtract 1 to be sure that we test if this
|
||||
// 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) {
|
||||
return {
|
||||
ele: $sibling,
|
||||
attachMethod: siblingY > eleY ? 'before' : 'after'
|
||||
@@ -105,12 +65,12 @@ $(document).ready(function() {
|
||||
ele: null,
|
||||
attachMethod: ''
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Information about the current drag.
|
||||
var dragState = {};
|
||||
|
||||
function onDragStart(draggie, event, pointer) {
|
||||
var onDragStart = function(draggie, event, pointer) {
|
||||
var ele = $(draggie.element);
|
||||
dragState = {
|
||||
// Where we started, in case of a failed drag
|
||||
@@ -122,9 +82,9 @@ $(document).ready(function() {
|
||||
// The list which will be expanded on hover
|
||||
toExpand: null
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
function onDragMove(draggie, event, pointer) {
|
||||
var onDragMove = function(draggie, event, pointer) {
|
||||
var ele = $(draggie.element);
|
||||
var destinationInfo = findDestination(ele);
|
||||
var destinationEle = destinationInfo.ele;
|
||||
@@ -139,29 +99,30 @@ $(document).ready(function() {
|
||||
clearTimeout(dragState.expandTimer);
|
||||
dragState.expandTimer = setTimeout(function() {
|
||||
parentList.removeClass('collapsed');
|
||||
}, 1000);
|
||||
parentList.find('.expand-collapse-icon').removeClass('expand').addClass('collapse');
|
||||
}, 400);
|
||||
dragState.toExpand = parentList;
|
||||
}
|
||||
// Clear out the old destination
|
||||
if(dragState.dropDestination) {
|
||||
dragState.dropDestination.removeClass('drop-target drop-target-prepend'
|
||||
+ ' drop-target-before drop-target-after');
|
||||
dragState.dropDestination.removeClass(droppableClasses);
|
||||
}
|
||||
// Mark the new destination
|
||||
if(destinationEle) {
|
||||
destinationEle.addClass('drop-target drop-target-' + destinationInfo.attachMethod);
|
||||
dragState.dropDestination = destinationEle;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function onDragEnd(draggie, event, pointer) {
|
||||
var onDragEnd = function(draggie, event, pointer) {
|
||||
var ele = $(draggie.element);
|
||||
|
||||
var destinationInfo = findDestination(ele);
|
||||
var destination = destinationInfo.ele;
|
||||
|
||||
// If the drag succeeded, rearrange the DOM and send the result.
|
||||
if(destination) {
|
||||
if(destination && pointer.x >= ele.offset().left
|
||||
&& pointer.x < ele.offset().left + ele.width()) {
|
||||
// Make sure we don't drop into a collapsed element
|
||||
if(destinationInfo.parentList) {
|
||||
destinationInfo.parentList.removeClass('collapsed');
|
||||
@@ -179,18 +140,16 @@ $(document).ready(function() {
|
||||
|
||||
// Clear dragging state in preparation for the next event.
|
||||
if(dragState.dropDestination) {
|
||||
dragState.dropDestination.removeClass('drop-target drop-target-prepend'
|
||||
+ ' drop-target-before drop-target-after');
|
||||
dragState.dropDestination.removeClass(droppableClasses);
|
||||
}
|
||||
clearTimeout(dragState.expandTimer);
|
||||
dragState = {};
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Find all parent-child changes and save them.
|
||||
*/
|
||||
function handleReorder(ele) {
|
||||
var itemID = ele.data('id');
|
||||
var handleReorder = function(ele) {
|
||||
var parentSelector = ele.data('parent-location-selector');
|
||||
var childrenSelector = ele.data('child-selector');
|
||||
var newParentEle = ele.parents(parentSelector).first();
|
||||
@@ -213,14 +172,14 @@ $(document).ready(function() {
|
||||
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.
|
||||
*/
|
||||
function saveItem(ele, childrenSelector, success) {
|
||||
var saveItem = function(ele, childrenSelector, success) {
|
||||
// Find all current child IDs.
|
||||
var children = _.map(
|
||||
ele.find(childrenSelector),
|
||||
@@ -239,5 +198,51 @@ $(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) {
|
||||
_.each(
|
||||
$(type),
|
||||
function(ele) {
|
||||
// Remember data necessary to reconstruct the parent-child relationships
|
||||
$(ele).data('droppable-class', droppableClass);
|
||||
$(ele).data('parent-location-selector', parentLocationSelector);
|
||||
$(ele).data('child-selector', type);
|
||||
var draggable = new Draggabilly(ele, {
|
||||
handle: handleClass,
|
||||
axis: 'y'
|
||||
});
|
||||
draggable.on('dragStart', onDragStart);
|
||||
draggable.on('dragMove', onDragMove);
|
||||
draggable.on('dragEnd', onDragEnd);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Section
|
||||
makeDraggable(
|
||||
'.courseware-section',
|
||||
'.section-drag-handle',
|
||||
'.courseware-overview',
|
||||
'article.courseware-overview'
|
||||
);
|
||||
// Subsection
|
||||
makeDraggable(
|
||||
'.id-holder',
|
||||
'.subsection-drag-handle',
|
||||
'.subsection-list > ol',
|
||||
'.courseware-section'
|
||||
);
|
||||
// Unit
|
||||
makeDraggable(
|
||||
'.unit',
|
||||
'.unit-drag-handle',
|
||||
'ol.sortable-unit-list',
|
||||
'li.branch, article.subsection-body'
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user