Browse Source

RenderQueue

Adam Shaw 8 years ago
parent
commit
8cd0c83a7a
3 changed files with 317 additions and 0 deletions
  1. 1 0
      src.json
  2. 128 0
      src/common/RenderQueue.js
  3. 188 0
      tests/automated-better/util/RenderQueue.js

+ 1 - 0
src.json

@@ -9,6 +9,7 @@
     "common/Model.js",
     "common/Promise.js",
     "common/TaskQueue.js",
+    "common/RenderQueue.js",
     "common/EmitterMixin.js",
     "common/ListenerMixin.js",
     "common/Popover.js",

+ 128 - 0
src/common/RenderQueue.js

@@ -0,0 +1,128 @@
+
+var RenderQueue = TaskQueue.extend({
+
+	waitsByNamespace: null,
+	waitNamespace: null,
+	waitId: null,
+
+
+	constructor: function(waitsByNamespace) {
+		TaskQueue.call(this); // super-constructor
+
+		this.waitsByNamespace = waitsByNamespace || {}
+	},
+
+
+	queue: function(taskFunc, namespace, type) {
+		var task = {
+			func: taskFunc,
+			namespace: namespace,
+			type: type
+		};
+		var waitMs;
+
+		if (namespace) {
+			waitMs = this.waitsByNamespace[namespace];
+		}
+
+		if (namespace && namespace === this.waitNamespace) {
+			this.delayWait(waitMs || 0);
+		}
+		else {
+			if (this.waitNamespace) {
+				this.clearWait();
+				this.tryStart();
+			}
+
+			if (waitMs) {
+				this.startWait(namespace, waitMs);
+			}
+		}
+
+		if (this.canStart()) {
+			this.q.push(task);
+			this.start();
+		}
+		else {
+			this.compoundTask(task);
+			this.tryStart();
+		}
+	},
+
+
+	startWait: function(namespace, waitMs) {
+		this.waitNamespace = namespace;
+		this.spawnWait(waitMs);
+	},
+
+
+	delayWait: function(waitMs) {
+		clearTimeout(this.waitId);
+		this.spawnWait(waitMs);
+	},
+
+
+	spawnWait: function(waitMs) {
+		var _this = this;
+
+		this.waitId = setTimeout(function() {
+			_this.waitNamespace = null;
+			_this.tryStart();
+		}, waitMs);
+	},
+
+
+	clearWait: function() {
+		if (this.waitNamespace) {
+			clearTimeout(this.waitId);
+			this.waitId = null;
+			this.waitNamespace = null;
+		}
+	},
+
+
+	canRunNext: function() {
+		return !this.waitNamespace &&
+			TaskQueue.prototype.canRunNext.apply(this, arguments);
+	},
+
+
+	runTask: function(task) {
+		this.runTaskFunc(task.func);
+	},
+
+
+	compoundTask: function(newTask) {
+		var q = this.q;
+		var shouldAppend = true;
+		var i, lastTask;
+
+		if (newTask.type === 'destroy') {
+
+			while (q.length) {
+				lastTask = q[q.length - 1];
+
+				if (newTask.namespace && newTask.namespace === lastTask.namespace) {
+
+					if (lastTask.type === 'add' || lastTask.type === 'remove') {
+						q.pop();
+						continue;
+					}
+					else if (lastTask.type === 'init') {
+						q.pop();
+						shouldAppend = false;
+					}
+				}
+
+				break;
+			}
+		}
+
+		if (shouldAppend) {
+			q.push(newTask);
+		}
+	}
+
+});
+
+FC.RenderQueue = RenderQueue;

+ 188 - 0
tests/automated-better/util/RenderQueue.js

@@ -0,0 +1,188 @@
+
+describe('RenderQueue', function() {
+	var RenderQueue = $.fullCalendar.RenderQueue;
+
+	it('executes atomic events in sequence', function() {
+		var ops = [];
+		var q = new RenderQueue();
+
+		q.queue(function() {
+			ops.push('fooinit');
+		}, 'foo', 'init');
+
+		q.queue(function() {
+			ops.push('fooremove');
+		}, 'foo', 'add');
+
+		q.queue(function() {
+			ops.push('fooadd');
+		}, 'foo', 'remove');
+
+		q.queue(function() {
+			ops.push('foodestroy');
+		}, 'foo', 'destroy');
+
+		expect(ops).toEqual([ 'fooinit', 'fooremove', 'fooadd', 'foodestroy' ]);
+	});
+
+	describe('when accumulating', function() {
+
+		describe('using clear action', function() {
+
+			it('destroys add/remove operations in same namespace', function() {
+				var ops = [];
+				var q = new RenderQueue();
+				q.pause();
+
+				q.queue(function() {
+					ops.push('fooadd');
+				}, 'foo', 'add');
+
+				q.queue(function() {
+					ops.push('fooremove');
+				}, 'foo', 'remove');
+
+				q.queue(function() {
+					ops.push('foodestroy');
+				}, 'foo', 'destroy');
+
+				expect(ops).toEqual([]);
+				q.resume();
+				expect(ops).toEqual([ 'foodestroy' ]);
+			});
+
+			it('is cancelled out by an init in same namespace', function() {
+				var ops = [];
+				var q = new RenderQueue();
+				q.pause();
+
+				q.queue(function() {
+					ops.push('barinit');
+				}, 'foo', 'init');
+
+				q.queue(function() {
+					ops.push('fooinit');
+				}, 'foo', 'init');
+
+				q.queue(function() {
+					ops.push('fooadd');
+				}, 'foo', 'add');
+
+				q.queue(function() {
+					ops.push('fooadd');
+				}, 'foo', 'remove');
+
+				q.queue(function() {
+					ops.push('fooadd');
+				}, 'foo', 'destroy');
+
+				expect(ops).toEqual([]);
+				q.resume();
+				expect(ops).toEqual([ 'barinit' ]);
+			});
+		});
+	});
+
+	describe('when namespace has a wait value', function() {
+
+		it('unpauses when done', function(done) {
+			var ops = [];
+			var q = new RenderQueue({
+				foo: 100
+			});
+
+			q.queue(function() {
+				ops.push('fooinit');
+			}, 'foo', 'init');
+
+			q.queue(function() {
+				ops.push('fooadd');
+			}, 'foo', 'add');
+
+			expect(ops).toEqual([]);
+
+			setTimeout(function() {
+				expect(ops).toEqual([ 'fooinit', 'fooadd' ]);
+				done();
+			}, 200);
+		});
+
+		it('restarts timer when new operation happens', function(done) {
+			var ops = [];
+			var q = new RenderQueue({
+				foo: 100
+			});
+
+			q.queue(function() {
+				ops.push('fooinit');
+			}, 'foo', 'init');
+
+ 			setTimeout(function() {
+				q.queue(function() {
+					ops.push('fooadd');
+				}, 'foo', 'add');
+			}, 50);
+
+			setTimeout(function() {
+				expect(ops).toEqual([]);
+			}, 125);
+
+			setTimeout(function() {
+				expect(ops).toEqual([ 'fooinit', 'fooadd' ]);
+				done();
+			}, 175);
+		});
+
+		it('synchronously executes queue when sync non-namespace operation happens', function() {
+			var ops = [];
+			var q = new RenderQueue({
+				foo: 100
+			});
+
+			q.queue(function() {
+				ops.push('fooinit');
+			}, 'foo', 'init');
+
+			q.queue(function() {
+				ops.push('fooadd');
+			}, 'foo', 'add');
+
+			expect(ops).toEqual([]);
+
+			q.queue(function() {
+				ops.push('barinit');
+			}, 'bar', 'init');
+
+			expect(ops).toEqual([ 'fooinit', 'fooadd', 'barinit' ]);
+		});
+
+		it('synchronously executes queue when async non-namespace operation happens', function(done) {
+			var ops = [];
+			var q = new RenderQueue({
+				foo: 100,
+				bar: 100
+			});
+
+			q.queue(function() {
+				ops.push('fooinit');
+			}, 'foo', 'init');
+
+			q.queue(function() {
+				ops.push('fooadd');
+			}, 'foo', 'add');
+
+			expect(ops).toEqual([]);
+
+			q.queue(function() {
+				ops.push('barinit');
+			}, 'bar', 'init');
+
+			expect(ops).toEqual([ 'fooinit', 'fooadd' ]);
+
+			setTimeout(function() {
+				expect(ops).toEqual([ 'fooinit', 'fooadd', 'barinit' ]);
+				done();
+			}, 200);
+		});
+	});
+});