diff --git a/.gitignore b/.gitignore index 0cfc89f..8409212 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ bower_components/ -node_modules/ \ No newline at end of file +node_modules/ +coverage/ diff --git a/bower.json b/bower.json index b9814ab..136f9da 100644 --- a/bower.json +++ b/bower.json @@ -17,10 +17,10 @@ ], "dependencies": { "angular": "~1.0.x", - "jquery-ui": ">= 1.9", - "jquery-simulate": "latest" + "jquery-ui": ">= 1.9" }, "devDependencies": { - "angular-mocks": "~1.0.x" + "angular-mocks": "~1.0.x", + "jquery-simulate": "latest" } } diff --git a/gruntFile.js b/gruntFile.js index 58b7d1a..798cbf5 100644 --- a/gruntFile.js +++ b/gruntFile.js @@ -1,4 +1,4 @@ -module.exports = function (grunt) { +module.exports = function(grunt) { grunt.loadNpmTasks('grunt-testacular'); grunt.loadNpmTasks('grunt-contrib-jshint'); @@ -7,8 +7,14 @@ module.exports = function (grunt) { grunt.registerTask('default', ['jshint', 'testacular']); var testacularConfig = function(configFile, customOptions) { - var options = { configFile: configFile, keepalive: true }; - var travisOptions = process.env.TRAVIS && { browsers: ['Firefox'], reporters: 'dots' }; + var options = { + configFile: configFile, + keepalive: true + }; + var travisOptions = process.env.TRAVIS && { + browsers: ['Firefox'], + reporters: 'dots' + }; return grunt.util._.extend(options, customOptions, travisOptions); }; @@ -19,19 +25,20 @@ module.exports = function (grunt) { options: testacularConfig('test/test.conf.js') } }, - jshint:{ - files:['src/**/*.js', 'test/**/*.js', 'demo/**/*.js'], - options:{ - curly:true, - eqeqeq:true, - immed:true, - latedef:true, - newcap:true, - noarg:true, - sub:true, - boss:true, - eqnull:true, - globals:{} + jshint: { + files: ['src/**/*.js', 'test/**/*.js', 'demo/**/*.js', '!test/libs/*.js'], + options: { + curly: true, + eqeqeq: true, + immed: true, + //indent: 2, + latedef: true, + newcap: true, + noarg: true, + sub: true, + boss: true, + eqnull: true, + globals: {} } } }); diff --git a/test/libs/jquery.simulate.dragandrevert.js b/test/libs/jquery.simulate.dragandrevert.js new file mode 100644 index 0000000..03cda66 --- /dev/null +++ b/test/libs/jquery.simulate.dragandrevert.js @@ -0,0 +1,60 @@ +;(function($, undefined) { + function findCenter(elem) { + var offset, + document = $(elem.ownerDocument); + elem = $(elem); + offset = elem.offset(); + + return { + x: offset.left + elem.outerWidth() / 2 - document.scrollLeft(), + y: offset.top + elem.outerHeight() / 2 - document.scrollTop() + }; + } + + $.extend($.simulate.prototype, { + simulateDragAndRevert: function() { + var i = 0, + target = this.target, + options = this.options, + center = findCenter(target), + x = Math.floor(center.x), + y = Math.floor(center.y), + dx = options.dx || 0, + dy = options.dy || 0, + moves = options.moves || 3, + coord = { + clientX: x, + clientY: y + }; + + this.simulateEvent(target, "mousedown", coord); + + for (; i < moves; i++) { + x += dx / moves; + y += dy / moves; + + coord = { + clientX: Math.round(x), + clientY: Math.round(y) + }; + + this.simulateEvent(document, "mousemove", coord); + } + + for (i = 0; i < moves; i++) { + x -= dx / moves; + y -= dy / moves; + + coord = { + clientX: Math.round(x), + clientY: Math.round(y) + }; + + this.simulateEvent(document, "mousemove", coord); + } + + this.simulateEvent(target, "mouseup", coord); + this.simulateEvent(target, "click", coord); + } + }); +})(jQuery); diff --git a/test/sortable.spec.js b/test/sortable.spec.js index 5a8d1cf..da60de6 100644 --- a/test/sortable.spec.js +++ b/test/sortable.spec.js @@ -3,6 +3,17 @@ describe('uiSortable', function() { // Ensure the sortable angular module is loaded beforeEach(module('ui.sortable')); + beforeEach(function() { + this.addMatchers({ + toEqualListContent: function (list) { + if (list && list.length) { + return list.children().map(function(){ return this.innerHTML; }).toArray(); + } + return []; + } + }); + }); + var EXTRA_DY_PERCENTAGE = 0.25; describe('Simple use', function() { @@ -30,7 +41,7 @@ describe('uiSortable', function() { }); }); - + describe('Drag & Drop simulation', function() { @@ -60,11 +71,13 @@ describe('uiSortable', function() { var dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); li.simulate('drag', { dy: dy }); expect($rootScope.items).toEqual(["One", "Three", "Two"]); + expect($rootScope.items).toEqualListContent(element); li = element.find(':eq(1)'); dy = -(1 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); li.simulate('drag', { dy: dy }); expect($rootScope.items).toEqual(["Three", "One", "Two"]); + expect($rootScope.items).toEqualListContent(element); $(element).remove(); }); @@ -92,23 +105,25 @@ describe('uiSortable', function() { var dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); li.simulate('drag', { dy: dy }); expect($rootScope.items.map(function(x){ return x.text; })).toEqual(["One", "Two", "Three", "Four"]); + expect($rootScope.items.map(function(x){ return x.text; })).toEqualListContent(element); li = element.find(':eq(1)'); dy = (2 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); li.simulate('drag', { dy: dy }); expect($rootScope.items.map(function(x){ return x.text; })).toEqual(["One", "Three", "Four", "Two"]); + expect($rootScope.items.map(function(x){ return x.text; })).toEqualListContent(element); - // fails on angular 1.2 li = element.find(':eq(2)'); dy = -(2 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); li.simulate('drag', { dy: dy }); expect($rootScope.items.map(function(x){ return x.text; })).toEqual(["Four", "One", "Three", "Two"]); + expect($rootScope.items.map(function(x){ return x.text; })).toEqualListContent(element); - // fails on angular 1.2 li = element.find(':eq(3)'); dy = -(2 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); li.simulate('drag', { dy: dy }); expect($rootScope.items.map(function(x){ return x.text; })).toEqual(["Four", "Two", "One", "Three"]); + expect($rootScope.items.map(function(x){ return x.text; })).toEqualListContent(element); // also placing right above the locked node seems a bit harder !?!? @@ -116,6 +131,121 @@ describe('uiSortable', function() { }); }); + it('should work when "placeholder" option is used', function() { + inject(function($compile, $rootScope) { + var element; + element = $compile('')($rootScope); + $rootScope.$apply(function() { + $rootScope.opts = { + placeholder: "sortable-item-placeholder" + }; + $rootScope.items = ["One", "Two", "Three"]; + }); + + host.append(element); + + var li = element.find(':eq(1)'); + var dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + li.simulate('drag', { dy: dy }); + expect($rootScope.items).toEqual(["One", "Three", "Two"]); + expect($rootScope.items).toEqualListContent(element); + + li = element.find(':eq(1)'); + dy = -(1 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + li.simulate('drag', { dy: dy }); + expect($rootScope.items).toEqual(["Three", "One", "Two"]); + expect($rootScope.items).toEqualListContent(element); + + $(element).remove(); + }); + }); + + it('should work when "placeholder" option equals the class of items', function() { + inject(function($compile, $rootScope) { + var element; + element = $compile('')($rootScope); + $rootScope.$apply(function() { + $rootScope.opts = { + placeholder: "sortable-item" + }; + $rootScope.items = ["One", "Two", "Three"]; + }); + + host.append(element); + + var li = element.find(':eq(1)'); + var dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + li.simulate('drag', { dy: dy }); + expect($rootScope.items).toEqual(["One", "Three", "Two"]); + expect($rootScope.items).toEqualListContent(element); + + li = element.find(':eq(1)'); + dy = -(1 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + li.simulate('drag', { dy: dy }); + expect($rootScope.items).toEqual(["Three", "One", "Two"]); + expect($rootScope.items).toEqualListContent(element); + + $(element).remove(); + }); + }); + + it('should continue to work after a drag is reverted', function() { + inject(function($compile, $rootScope) { + var element; + element = $compile('')($rootScope); + $rootScope.$apply(function() { + $rootScope.opts = { + placeholder: "sortable-item" + }; + $rootScope.items = ["One", "Two", "Three"]; + }); + + host.append(element); + + var li = element.find(':eq(0)'); + var dy = (2 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + li.simulate('dragAndRevert', { dy: dy }); + expect($rootScope.items).toEqual(["One", "Two", "Three"]); + expect($rootScope.items).toEqualListContent(element); + + li = element.find(':eq(0)'); + dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + li.simulate('drag', { dy: dy }); + expect($rootScope.items).toEqual(["Two", "One", "Three"]); + expect($rootScope.items).toEqualListContent(element); + + li = element.find(':eq(1)'); + dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + li.simulate('drag', { dy: dy }); + expect($rootScope.items).toEqual(["Two", "Three", "One"]); + expect($rootScope.items).toEqualListContent(element); + + li = element.find(':eq(1)'); + dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + li.simulate('drag', { dy: dy }); + expect($rootScope.items).toEqual(["Two", "One", "Three"]); + expect($rootScope.items).toEqualListContent(element); + + $(element).remove(); + }); + }); + + }); + + describe('Multiple sortables related', function() { + + var host; + + beforeEach(inject(function() { + host = $('
'); + $('body').append(host); + })); + + afterEach(function() { + host.remove(); + host = null; + }); + it('should update model when sorting between sortables', function() { inject(function($compile, $rootScope) { var elementTop, elementBottom; @@ -129,21 +259,101 @@ describe('uiSortable', function() { host.append(elementTop).append(elementBottom); - // fails on angular 1.2 var li1 = elementTop.find(':eq(0)'); var li2 = elementBottom.find(':eq(0)'); var dy = EXTRA_DY_PERCENTAGE * li1.outerHeight() + (li2.position().top - li1.position().top); li1.simulate('drag', { dy: dy }); expect($rootScope.itemsTop).toEqual(["Top Two", "Top Three"]); expect($rootScope.itemsBottom).toEqual(["Bottom One", "Top One", "Bottom Two", "Bottom Three"]); + expect($rootScope.itemsTop).toEqualListContent(elementTop); + expect($rootScope.itemsBottom).toEqualListContent(elementBottom); - // fails on angular 1.2 li1 = elementBottom.find(':eq(1)'); li2 = elementTop.find(':eq(1)'); dy = -EXTRA_DY_PERCENTAGE * li1.outerHeight() - (li1.position().top - li2.position().top); li1.simulate('drag', { dy: dy }); expect($rootScope.itemsTop).toEqual(["Top Two", "Top One", "Top Three"]); expect($rootScope.itemsBottom).toEqual(["Bottom One", "Bottom Two", "Bottom Three"]); + expect($rootScope.itemsTop).toEqualListContent(elementTop); + expect($rootScope.itemsBottom).toEqualListContent(elementBottom); + + $(elementTop).remove(); + $(elementBottom).remove(); + }); + }); + + it('should work when "placeholder" option is used', function() { + inject(function($compile, $rootScope) { + var elementTop, elementBottom; + elementTop = $compile('')($rootScope); + elementBottom = $compile('')($rootScope); + $rootScope.$apply(function() { + $rootScope.itemsTop = ["Top One", "Top Two", "Top Three"]; + $rootScope.itemsBottom = ["Bottom One", "Bottom Two", "Bottom Three"]; + $rootScope.opts = { + placeholder: "sortable-item-placeholder", + connectWith: ".cross-sortable" + }; + }); + + host.append(elementTop).append(elementBottom); + + var li1 = elementTop.find(':eq(0)'); + var li2 = elementBottom.find(':eq(0)'); + var dy = EXTRA_DY_PERCENTAGE * li1.outerHeight() + (li2.position().top - li1.position().top); + li1.simulate('drag', { dy: dy }); + expect($rootScope.itemsTop).toEqual(["Top Two", "Top Three"]); + expect($rootScope.itemsBottom).toEqual(["Bottom One", "Top One", "Bottom Two", "Bottom Three"]); + expect($rootScope.itemsTop).toEqualListContent(elementTop); + expect($rootScope.itemsBottom).toEqualListContent(elementBottom); + + li1 = elementBottom.find(':eq(1)'); + li2 = elementTop.find(':eq(1)'); + dy = -EXTRA_DY_PERCENTAGE * li1.outerHeight() - (li1.position().top - li2.position().top); + li1.simulate('drag', { dy: dy }); + expect($rootScope.itemsTop).toEqual(["Top Two", "Top One", "Top Three"]); + expect($rootScope.itemsBottom).toEqual(["Bottom One", "Bottom Two", "Bottom Three"]); + expect($rootScope.itemsTop).toEqualListContent(elementTop); + expect($rootScope.itemsBottom).toEqualListContent(elementBottom); + + $(elementTop).remove(); + $(elementBottom).remove(); + }); + }); + + it('should work when "placeholder" option equals the class of items', function() { + inject(function($compile, $rootScope) { + var elementTop, elementBottom; + elementTop = $compile('')($rootScope); + elementBottom = $compile('')($rootScope); + $rootScope.$apply(function() { + $rootScope.itemsTop = ["Top One", "Top Two", "Top Three"]; + $rootScope.itemsBottom = ["Bottom One", "Bottom Two", "Bottom Three"]; + $rootScope.opts = { + placeholder: "sortable-item", + connectWith: ".cross-sortable" + }; + }); + + host.append(elementTop).append(elementBottom); + + var li1 = elementTop.find(':eq(0)'); + var li2 = elementBottom.find(':eq(0)'); + var dy = EXTRA_DY_PERCENTAGE * li1.outerHeight() + (li2.position().top - li1.position().top); + li1.simulate('drag', { dy: dy }); + expect($rootScope.itemsTop).toEqual(["Top Two", "Top Three"]); + expect($rootScope.itemsBottom).toEqual(["Bottom One", "Top One", "Bottom Two", "Bottom Three"]); + expect($rootScope.itemsTop).toEqualListContent(elementTop); + expect($rootScope.itemsBottom).toEqualListContent(elementBottom); + + li1 = elementBottom.find(':eq(1)'); + li2 = elementTop.find(':eq(1)'); + dy = -EXTRA_DY_PERCENTAGE * li1.outerHeight() - (li1.position().top - li2.position().top); + li1.simulate('drag', { dy: dy }); + expect($rootScope.itemsTop).toEqual(["Top Two", "Top One", "Top Three"]); + expect($rootScope.itemsBottom).toEqual(["Bottom One", "Bottom Two", "Bottom Three"]); + expect($rootScope.itemsTop).toEqualListContent(elementTop); + expect($rootScope.itemsBottom).toEqualListContent(elementBottom); $(elementTop).remove(); $(elementBottom).remove(); @@ -188,16 +398,87 @@ describe('uiSortable', function() { var dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); li.simulate('drag', { dy: dy }); expect($rootScope.items).toEqual(["One", "Two", "Three"]); + expect($rootScope.items).toEqualListContent(element); li = element.find(':eq(0)'); dy = (2 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); li.simulate('drag', { dy: dy }); expect($rootScope.items).toEqual(["Two", "Three", "One"]); + expect($rootScope.items).toEqualListContent(element); + + li = element.find(':eq(2)'); + dy = -(2 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + li.simulate('drag', { dy: dy }); + expect($rootScope.items).toEqual(["One", "Two", "Three"]); + expect($rootScope.items).toEqualListContent(element); $(element).remove(); }); }); + // it('should cancel sorting of nodes that contain "Two"', function() { + // inject(function($compile, $rootScope) { + // var elementTop, elementBottom; + // elementTop = $compile('')($rootScope); + // elementBottom = $compile('')($rootScope); + // $rootScope.$apply(function() { + // $rootScope.itemsTop = ["Top One", "Top Two", "Top Three"]; + // $rootScope.itemsBottom = ["Bottom One", "Bottom Two", "Bottom Three"]; + // $rootScope.opts = { + // connectWith: ".cross-sortable", + // update: function(e, ui) { + // if (ui.item.scope() && + // (typeof ui.item.scope().item === 'string') && + // ui.item.scope().item.indexOf("Two") >= 0) { + // ui.item.parent().sortable('cancel'); + // } + // } + // }; + // }); + + // host.append(elementTop).append(elementBottom); + + // var li1 = elementTop.find(':eq(1)'); + // var li2 = elementBottom.find(':eq(0)'); + // var dy = EXTRA_DY_PERCENTAGE * li1.outerHeight() + (li2.position().top - li1.position().top); + // li1.simulate('drag', { dy: dy }); + // expect($rootScope.itemsTop).toEqual(["Top One", "Top Two", "Top Three"]); + // expect($rootScope.itemsBottom).toEqual(["Bottom One", "Bottom Two", "Bottom Three"]); + // expect($rootScope.itemsTop).toEqualListContent(elementTop); + // expect($rootScope.itemsBottom).toEqualListContent(elementBottom); + + // li1 = elementBottom.find(':eq(1)'); + // li2 = elementTop.find(':eq(1)'); + // dy = -EXTRA_DY_PERCENTAGE * li1.outerHeight() - (li1.position().top - li2.position().top); + // li1.simulate('drag', { dy: dy }); + // expect($rootScope.itemsTop).toEqual(["Top One", "Top Two", "Top Three"]); + // expect($rootScope.itemsBottom).toEqual(["Bottom One", "Bottom Two", "Bottom Three"]); + // expect($rootScope.itemsTop).toEqualListContent(elementTop); + // expect($rootScope.itemsBottom).toEqualListContent(elementBottom); + + // li1 = elementTop.find(':eq(0)'); + // li2 = elementBottom.find(':eq(0)'); + // dy = EXTRA_DY_PERCENTAGE * li1.outerHeight() + (li2.position().top - li1.position().top); + // li1.simulate('drag', { dy: dy }); + // expect($rootScope.itemsTop).toEqual(["Top Two", "Top Three"]); + // expect($rootScope.itemsBottom).toEqual(["Bottom One", "Top One", "Bottom Two", "Bottom Three"]); + // expect($rootScope.itemsTop).toEqualListContent(elementTop); + // expect($rootScope.itemsBottom).toEqualListContent(elementBottom); + + // li1 = elementBottom.find(':eq(1)'); + // li2 = elementTop.find(':eq(1)'); + // dy = -EXTRA_DY_PERCENTAGE * li1.outerHeight() - (li1.position().top - li2.position().top); + // li1.simulate('drag', { dy: dy }); + // expect($rootScope.itemsTop).toEqual(["Top Two", "Top One", "Top Three"]); + // expect($rootScope.itemsBottom).toEqual(["Bottom One", "Bottom Two", "Bottom Three"]); + // expect($rootScope.itemsTop).toEqualListContent(elementTop); + // expect($rootScope.itemsBottom).toEqualListContent(elementBottom); + + // $(elementTop).remove(); + // $(elementBottom).remove(); + // }); + // }); + it('should update model from update() callback', function() { inject(function($compile, $rootScope) { var element, logsElement; @@ -220,7 +501,8 @@ describe('uiSortable', function() { li.simulate('drag', { dy: dy }); expect($rootScope.items).toEqual(["One", "Three", "Two"]); expect($rootScope.logs).toEqual(["Moved element Two"]); - expect(logsElement.find('li').html()).toEqual("Moved element Two"); + expect($rootScope.items).toEqualListContent(element); + expect($rootScope.logs).toEqualListContent(logsElement); $(element).remove(); $(logsElement).remove(); @@ -250,7 +532,8 @@ describe('uiSortable', function() { li.simulate('drag', { dy: dy }); expect($rootScope.items).toEqual(["One", "Three", "Two"]); expect($rootScope.logs).toEqual(["Moved element Two"]); - expect(logsElement.find('li').html()).toEqual("Moved element Two"); + expect($rootScope.items).toEqualListContent(element); + expect($rootScope.logs).toEqualListContent(logsElement); $(element).remove(); $(logsElement).remove(); diff --git a/test/test.conf.js b/test/test.conf.js index 492223a..8eb9a9e 100644 --- a/test/test.conf.js +++ b/test/test.conf.js @@ -4,6 +4,7 @@ files = [ JASMINE_ADAPTER, 'bower_components/jquery/jquery.js', 'bower_components/jquery-simulate/jquery.simulate.js', + 'test/libs/jquery.simulate.dragandrevert.js', 'bower_components/jquery-ui/ui/jquery-ui.js', 'bower_components/angular/angular.js', 'bower_components/angular-mocks/angular-mocks.js', @@ -11,4 +12,15 @@ files = [ 'test/*.spec.js' ]; singleRun = true; -browsers = [ 'Chrome' ]; \ No newline at end of file +autoWatch = false; +browsers = [ 'Chrome' ]; +reporters = [ 'dots' ]; + +if (singleRun) { + reporters.push('coverage'); + preprocessors = { '**/src/*.js': 'coverage' }; + coverageReporter = { + type : 'html', + dir : 'coverage/' + }; +}