diff --git a/Gruntfile.js b/Gruntfile.js index 5d67184bb4155def9e7afaa6d4071a25bfbb3649..83f37bc76d5f1360c633d266b3dd3d3dde5c5d97 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -18,7 +18,11 @@ module.exports = function (grunt) { }, jshint: { - all: ['*.js', '!*.min.js'], + all: [ + '*.js', + '!*.min.js', + 'tests/**/*.js' + ], options: { jshintrc: true @@ -48,7 +52,25 @@ module.exports = function (grunt) { } }, - jquery: {} + jquery: {}, + + qunit: { + all: ['tests/index.html'], + options: { + '--web-security': 'no', + coverage: { + src: ['Sortable.js'], + instrumentedFiles: 'temp/', + htmlReport: 'report/coverage/', + coberturaReport: 'report/', + baseUrl: "./coverage", + linesThresholdPct: 99, + functionsThresholdPct: 100, + branchesThresholdPct: 90, + statementsThresholdPct: 90 + } + } + } }); @@ -91,6 +113,7 @@ module.exports = function (grunt) { grunt.loadNpmTasks('grunt-version'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-qunit-istanbul'); grunt.loadNpmTasks('grunt-exec'); // Meteor tasks @@ -98,6 +121,6 @@ module.exports = function (grunt) { grunt.registerTask('meteor-publish', 'exec:meteor-publish'); grunt.registerTask('meteor', ['meteor-test', 'meteor-publish']); - grunt.registerTask('tests', ['jshint']); + grunt.registerTask('tests', ['jshint', 'qunit']); grunt.registerTask('default', ['tests', 'version', 'uglify:dist']); }; diff --git a/tests/index.html b/tests/index.html index 00bfd212f23aaa12d5ad7784cbb21595be4438b5..773c6c9a7f27a22c96789020b886434948131013 100644 --- a/tests/index.html +++ b/tests/index.html @@ -8,32 +8,27 @@ Sortable :: Tests -
- -
- + + +
-
- - + + - + - - - - - - diff --git a/tests/sortable.tests.js b/tests/sortable.tests.js index cfdd6fdf36d05f5c1e51e15f4a8304118c65182c..f15119fc9d23c44ee19bae8fd77524d2956c14e3 100644 --- a/tests/sortable.tests.js +++ b/tests/sortable.tests.js @@ -1,18 +1,99 @@ +/* global QUnit, fixture, Sortable, simulateDrag */ + 'use strict'; QUnit.module('Sortable'); -QUnit.sortableTest('simple', { - from: { - el: '#simple', - index: 0 - }, +function createSortableList(options) { + var listEl = document.createElement('div'); + var length = options.length; + var withHandle = options.withHandle; + + if (withHandle) { + options.handle = withHandle; + } + + listEl.className = 'js-list'; + + for (var i = 0; i < length; i++) { + var el = document.createElement('div'); - to: { - el: '#simple', - index: 'last' + el.appendChild(document.createTextNode('Item ' + (i + 1))); + el.className = 'js-list-item'; + + listEl.appendChild(el); } -}, function (assert, scope) { - assert.ok(scope.toList.contains(scope.target), 'container'); - assert.equal(scope.target, scope.toList.lastElementChild, 'position'); + + fixture.appendChild(listEl); + + return { + el: listEl, + sortable: Sortable.create(listEl, options), + destroy: function () { + this.sortable.destroy(); + fixture.removeChild(this.el); + + this.el = null; + this.sortable = null; + } + }; +} + + +QUnit.test('core', function (assert) { + var done = assert.async(); + var events = {}; + var logEvent = function (evt) { events[this + evt.type] = true; }; + var list = createSortableList({ + length: 3, + onStart: logEvent.bind('on'), + onMove: logEvent.bind('on'), + onUpdate: logEvent.bind('on'), + onEnd: logEvent.bind('on') + }); + + list.el.addEventListener('start', logEvent.bind('')); + list.el.addEventListener('move', logEvent.bind('')); + list.el.addEventListener('update', logEvent.bind('')); + list.el.addEventListener('end', logEvent.bind('')); + + simulateDrag({ + from: { + el: list.el, + index: 0 + }, + + to: {index: 'last'}, + + ontap: function () { + assert.ok(list.el.firstChild.className.indexOf('sortable-chosen') > -1, 'sortable-choose'); + }, + + ondragstart: function () { + setTimeout(function () { + assert.ok(list.el.firstChild.className.indexOf('sortable-ghost') > -1, 'sortable-ghost'); + }, 0); + } + }, function () { + assert.deepEqual(Object.keys(events), [ + 'start', 'onstart', + 'move', 'onmove', + 'update', 'onupdate', + 'end', 'onend' + ]); + + list.destroy(); + done(); + }); }); + + +//QUnit.test('handle', function (assert) { +// var done = assert.async(); +// var list = createSortableList({ +// length: 3, +// withHandle: true, +// onStart: function () { assert.ok(false, 'start'); }, +// onEnd: function () { assert.ok(false, 'end'); } +// }); +//}); diff --git a/tests/src/raf.js b/tests/src/polyfills.js similarity index 62% rename from tests/src/raf.js rename to tests/src/polyfills.js index f6773d5df6409ec4f1274bfb43fe18041e70415d..fb97f72bd7c00acc8887ea1541e81536f1d463a0 100644 --- a/tests/src/raf.js +++ b/tests/src/polyfills.js @@ -36,3 +36,31 @@ }; } }()); + + +if (!Function.prototype.bind) { + Function.prototype.bind = function (oThis) { + 'use strict'; + + if (typeof this !== 'function') { + // ближайший аналог внутренней функции + // IsCallable в ECMAScript 5 + throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); + } + + var aArgs = Array.prototype.slice.call(arguments, 1), + fToBind = this, + fNOP = function () {}, + fBound = function () { + return fToBind.apply(this instanceof fNOP && oThis + ? this + : oThis, + aArgs.concat(Array.prototype.slice.call(arguments))); + }; + + fNOP.prototype = this.prototype; + fBound.prototype = new fNOP(); + + return fBound; + }; +} diff --git a/tests/src/qunit.ext.js b/tests/src/qunit.ext.js deleted file mode 100644 index 3ac10aff99dff137ada5816292a6feaf55a353eb..0000000000000000000000000000000000000000 --- a/tests/src/qunit.ext.js +++ /dev/null @@ -1,13 +0,0 @@ -(function () { - 'use strict'; - - QUnit.sortableTest = function sortableTest(name, options, fn) { - QUnit.test(name, function (assert) { - var done = assert.async(); - var data = simulateDrag(options, function () { - fn(assert, data, options); - done(); - }); - }); - }; -})(); diff --git a/tests/src/simulate.js b/tests/src/simulate.js index e080e31496e7b95bda4e31987a3b0d7808e54c75..1cd43e4b0eb0e0382560bee95bdb52064770a36d 100644 --- a/tests/src/simulate.js +++ b/tests/src/simulate.js @@ -42,7 +42,9 @@ } function getTraget(target) { - var children = document.getElementById(target.el.substr(1)).children; + var el = typeof target.el === 'string' ? document.getElementById(target.el.substr(1)) : target.el; + var children = el.children; + return ( children[target.index] || children[target.index === 'first' ? 0 : -1] || @@ -58,16 +60,18 @@ return { x: rect.left, y: rect.top, - cx: rect.left + width/2, - cy: rect.top + height/2, + cx: rect.left + width / 2, + cy: rect.top + height / 2, w: width, h: height, - hw: width/2, - wh: height/2 - } + hw: width / 2, + wh: height / 2 + }; } function simulateDrag(options, callback) { + options.to.el = options.to.el || options.from.el; + var fromEl = getTraget(options.from); var toEl = getTraget(options.to); @@ -83,10 +87,16 @@ var duration = options.duration || 1000; simulateEvent(fromEl, 'mousedown', {button: 0}); + options.ontap && options.ontap(); + simulateEvent(toEl, 'dragstart'); + requestAnimationFrame(function () { + options.ondragstart && options.ondragstart(); + }); + requestAnimationFrame(function loop() { - var progress = (new Date().getTime() - startTime)/duration; + var progress = (new Date().getTime() - startTime) / duration; var x = fromRect.cx + (toRect.cx - fromRect.cx) * progress; var y = fromRect.cy + (toRect.cy - fromRect.cy) * progress; var overEl = fromEl.ownerDocument.elementFromPoint(x, y); @@ -95,6 +105,7 @@ dotEl.style.left = x + 'px'; dotEl.style.top = y + 'px'; + //console.log(overEl.parentNode.parentNode.parentNode.id, overEl.className, x, y); overEl && simulateEvent(overEl, 'dragover', { clientX: x, clientY: y @@ -104,6 +115,7 @@ dotEl.style.display = ''; requestAnimationFrame(loop); } else { + options.ondragend && options.ondragend(); simulateEvent(toEl, 'drop'); callback(); }