summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGeorge Crawford <g.o.crawford@gmail.com>2013-10-08 10:34:53 +0100
committerGeorge Crawford <g.o.crawford@gmail.com>2013-10-08 10:34:53 +0100
commit3d33c839cdb7ce38ba5b0f887814279181d663fb (patch)
tree135371dc1f732485eb59281c9110585d791292f8
downloadfastdom-3d33c839cdb7ce38ba5b0f887814279181d663fb.zip
fastdom-3d33c839cdb7ce38ba5b0f887814279181d663fb.tar.gz
fastdom-3d33c839cdb7ce38ba5b0f887814279181d663fb.tar.bz2
First pages commit
-rw-r--r--.gitignore2
-rw-r--r--.npmignore3
-rw-r--r--.travis.yml7
-rw-r--r--History.md14
-rw-r--r--README.md141
-rw-r--r--bower.json15
-rw-r--r--component.json15
-rw-r--r--examples/animation.html120
-rw-r--r--examples/aspect-ratio.html96
-rw-r--r--index.js289
-rw-r--r--package.json25
-rw-r--r--test/index.html28
-rw-r--r--test/setup.js18
-rw-r--r--test/test.clear.js97
-rw-r--r--test/test.defer.js50
-rw-r--r--test/test.set.js187
16 files changed, 1107 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..28f1ba7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+.DS_Store \ No newline at end of file
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..a806a83
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,3 @@
+/node_modules/
+/examples/
+/test/ \ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..6212d53
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,7 @@
+script:
+ - "npm test"
+
+language: node_js
+
+node_js:
+ - "0.10" \ No newline at end of file
diff --git a/History.md b/History.md
new file mode 100644
index 0000000..68eeb5f
--- /dev/null
+++ b/History.md
@@ -0,0 +1,14 @@
+
+0.7.1 / 2013-10-05
+==================
+
+ * fix memory leaks with undeleted refs
+ * fix context not being passed to `.defer` jobs
+
+0.7.0 / 2013-10-05
+==================
+
+ * add `FastDom#clear` clears read, write and defer jobs by id
+ * remove `FastDom#clearRead`
+ * remove `FastDom#clearWrite`
+ * change directory structure by removing `/lib` \ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bd01a18
--- /dev/null
+++ b/README.md
@@ -0,0 +1,141 @@
+# fastdom [![Build Status](https://travis-ci.org/wilsonpage/fastdom.png?branch=master)](https://travis-ci.org/wilsonpage/fastdom)
+
+Eliminates layout thrashing by batching DOM read/write operations (~750 bytes gzipped).
+
+```js
+var fastdom = new FastDom();
+
+fastdom.read(function() {
+ console.log('read');
+});
+
+fastdom.write(function() {
+ console.log('write');
+});
+
+fastdom.read(function() {
+ console.log('read');
+});
+
+fastdom.write(function() {
+ console.log('write');
+});
+```
+
+Outputs:
+
+```
+read
+read
+write
+write
+```
+
+## Examples
+
+- [Aspect ratio example](http://wilsonpage.github.io/fastdom/examples/aspect-ratio.html)
+
+## Installation
+
+FastDom is CommonJS and AMD compatible, you can install it in one of the following ways:
+
+```
+$ npm install fastdom
+```
+```
+$ bower install fastdom
+```
+```
+$ component install wilsonpage/fastdom
+```
+or [download](http://github.com/wilsonpage/fastdom/raw/master/index.js).
+
+## How it works
+
+FastDom works as a regulatory layer between your app/library and the DOM. By batching DOM access we **avoid unnecessary document reflows and speed up layout perfomance dramatically**.
+
+Each read/write job is added to a corresponding read/write queue. The queues are emptied (reads, then writes) at the turn of the next frame using [`window.requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame).
+
+FastDom aims to behave like a singleton across *all* modules in your app. When any module requires `'fastdom'` they get the same instance back, meaning FastDom can harmonize DOM access app-wide.
+
+Potentially a third-party library could depend on FastDom, and better integrate within an app that itself uses it.
+
+## API
+
+### FastDom#read(callback[, context])
+
+Schedules a job for the 'read' queue. Returns a unique ID that can be used to clear the scheduled job.
+
+```js
+fastdom.read(function() {
+ var width = element.clientWidth;
+});
+```
+
+### FastDom#write(callback[, context])
+
+Schedules a job for the 'write' queue. Returns a unique ID that can be used to clear the scheduled job.
+
+```js
+fastdom.write(function() {
+ element.style.width = width + 'px';
+});
+```
+
+### FastDom#defer(frames, callback[, context])
+
+Defers a job for the number of frames specified. This is useful is you have a particualrly expensive piece of work to do, and don't want it to be done with all the other work.
+
+For example; you are using third party library that doesn't expose an API that allows you split DOM read/write work, `fastdom.defer()` will push this work futher into the future and prevent it from disrupting other carefully batched work.
+
+```js
+fastdom.defer(3, expensiveStuff);
+```
+
+### FastDom#clear(id)
+
+Clears **any** scheduled job by id.
+
+```js
+var read = fastdom.read(function(){});
+var write = fastdom.write(function(){});
+var defer = fastdom.defer(4, function(){});
+
+fastdom.clear(read);
+fastdom.clear(write);
+fastdom.clear(defer);
+```
+
+## Tests
+
+#### With PhantomJS
+
+```
+$ npm install
+$ npm test
+```
+
+#### Without PhantomJS
+
+Open `test/index.html` in your browser.
+
+## Author
+
+- **Wilson Page** - [@wilsonpage](http://github.com/wilsonpage)
+
+## Contributors
+
+- **Wilson Page** - [@wilsonpage](http://github.com/wilsonpage)
+- **George Crawford** - [@georgecrawford](http://github.com/georgecrawford)
+
+## License
+
+(The MIT License)
+
+Copyright (c) 2013 Wilson Page <wilsonpage@me.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..d318c23
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,15 @@
+{
+ "name": "fastdom",
+ "description": "Eliminates layout thrashing by batching DOM read/write operations",
+ "version": "0.7.1",
+ "main": "index.js",
+ "scripts": [
+ "index.js"
+ ],
+ "ignore": [
+ "examples/",
+ "test/",
+ "README.md"
+ ],
+ "license": "MIT"
+}
diff --git a/component.json b/component.json
new file mode 100644
index 0000000..82aa51f
--- /dev/null
+++ b/component.json
@@ -0,0 +1,15 @@
+{
+ "name": "fastdom",
+ "description": "Eliminates layout thrashing by batching DOM read/write operations",
+ "version": "0.7.1",
+ "main": "index.js",
+ "scripts": [
+ "index.js"
+ ],
+ "ignore": [
+ "examples/",
+ "test/",
+ "README.md"
+ ],
+ "license": "MIT"
+} \ No newline at end of file
diff --git a/examples/animation.html b/examples/animation.html
new file mode 100644
index 0000000..9f9b80c
--- /dev/null
+++ b/examples/animation.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>FastDom: Animation Example</title>
+ <script type="text/javascript" src="../index.js"></script>
+ </head>
+ <body>
+
+ <style>
+ .mover {
+ background: url("data:image/png;base64,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");
+ height: 100px;
+ width: 100px;
+ position: absolute;
+ }
+
+ button.active {
+ color: red;
+ }
+
+ </style>
+
+ <label>Number of elements <input id="count" type="text" value="400" /></label>
+ <button id="sync" class="active">Forced synchronous layout</button>
+ <button id="async">Run with FastDom</button>
+ <button id="noread">Avoid DOM read</button>
+ <button id="toggle">Start</button>
+
+ <div id='test'></div>
+
+ <script>
+
+ var moveMethod = 'sync',
+ count = document.getElementById('count'),
+ test = document.getElementById('test'),
+ timestamp, raf, movers;
+
+ var mover = {
+ sync: function(m) {
+ mover.setLeft(m, movers[m].offsetTop);
+ },
+ async: function(m) {
+ fastdom.read(function() {
+ var top = movers[m].offsetTop;
+ fastdom.write(function() {
+ mover.setLeft(m, top);
+ });
+ });
+ },
+ noread: function(m) {
+ mover.setLeft(m, m);
+ },
+ setLeft: function(m, top) {
+ movers[m].style.left = ((Math.sin(top + timestamp/1000)+1) * 500) + 'px';
+ }
+ };
+
+ function update(thisTimestamp) {
+ timestamp = thisTimestamp;
+ for (var m = 0; m < movers.length; m++) {
+ mover[moveMethod](m);
+ }
+ raf = window.requestAnimationFrame(update);
+ }
+
+ function toggleAnim(e) {
+
+ var html, num;
+
+ if (raf) {
+
+ window.cancelAnimationFrame(raf);
+ raf = false;
+ e.currentTarget.innerHTML = 'Start';
+ count.disabled = false;
+
+ } else {
+
+ html = '';
+ num = count.value;
+
+ for (i = 0; i < num; i++) {
+ html += '<div class="mover"></div>';
+ }
+ test.innerHTML = html;
+
+ movers = test.querySelectorAll('.mover');
+ movers[0].style.top = '50px';
+ for (var m = 1; m < movers.length; m++) {
+ movers[m].style.top = (m * 20) + 'px';
+ }
+
+ raf = window.requestAnimationFrame(update);
+ e.currentTarget.innerHTML = 'Stop';
+ count.disabled = true;
+ }
+ }
+
+ function setMethod(method) {
+ document.getElementById(moveMethod).classList.remove('active');
+ document.getElementById(method).classList.add('active');
+ moveMethod = method;
+ }
+
+ document.getElementById('toggle').addEventListener('click', toggleAnim);
+ document.getElementById('sync').addEventListener('click', function() {
+ setMethod('sync');
+ });
+ document.getElementById('async').addEventListener('click', function() {
+ setMethod('async');
+ });
+ document.getElementById('noread').addEventListener('click', function() {
+ setMethod('noread');
+ });
+
+ </script>
+
+
+ </body>
+</html>
diff --git a/examples/aspect-ratio.html b/examples/aspect-ratio.html
new file mode 100644
index 0000000..25ee73e
--- /dev/null
+++ b/examples/aspect-ratio.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title>FastDom: Aspect Ratio Example</title>
+<style>
+
+ * {
+ box-sizing: border-box;
+ }
+
+ div {
+ float: left;
+ background: silver;
+ border: solid 2px white;
+ }
+
+</style>
+</head>
+<body>
+ <label>Number of elements <input id="input" type="text" value="100" /></label>
+ <button id="withoutFastDom">Run without FastDom</button>
+ <button id="withFastDom">Run with FastDom</button>
+ <button id="resetbtn">reset</button>
+ <section id="perf"></section>
+ <section id="container"></section>
+ <script type="text/javascript" src="../index.js"></script>
+ <script>
+ var n = input.value;
+ var start;
+ var divs;
+
+ // Setup
+ function reset() {
+ divs = [];
+ container.innerHTML = '';
+
+ for (var i = 0; i < n; i++) {
+ var div = document.createElement('div');
+ div.style.width = Math.round(Math.random() * window.innerWidth) + 'px';
+ container.appendChild(div);
+ divs.push(div);
+ }
+ }
+
+ function setAspect(div, i) {
+ var aspect = 9/16;
+ var isLast = i === (n - 1)
+ var h = div.clientWidth * aspect;
+
+ div.style.height = h + 'px';
+
+ if (isLast) {
+ displayPerf(performance.now() - start);
+ }
+ }
+
+ function setAspectFastDom(div, i) {
+ var aspect = 9/16;
+ var isLast = i === (n - 1)
+
+ // READ
+ fastdom.read(function() {
+ var h = div.clientWidth * aspect;
+
+ // WRITE
+ fastdom.write(function() {
+ div.style.height = h + 'px';
+
+ if (isLast) {
+ displayPerf(performance.now() - start);
+ }
+ });
+ });
+ }
+
+ function displayPerf(ms) {
+ perf.textContent = ms + 'ms';
+ }
+
+ withoutFastDom.onclick = function() {
+ reset();
+ start = performance.now();
+ divs.forEach(setAspect);
+ };
+
+ withFastDom.onclick = function() {
+ reset();
+ start = performance.now();
+ divs.forEach(setAspectFastDom);
+ };
+
+ resetbtn.onclick = reset;
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..6be22f8
--- /dev/null
+++ b/index.js
@@ -0,0 +1,289 @@
+
+/**
+ * DOM-Batch
+ *
+ * Eliminates layout thrashing
+ * by batching DOM read/write
+ * interactions.
+ *
+ * @author Wilson Page <wilsonpage@me.com>
+ */
+
+;(function(fastdom){
+
+ 'use strict';
+
+ // Normalize rAF
+ var raf = window.requestAnimationFrame
+ || window.webkitRequestAnimationFrame
+ || window.mozRequestAnimationFrame
+ || window.msRequestAnimationFrame
+ || function(cb) { return window.setTimeout(cb, 1000 / 60); };
+
+ // Normalize cAF
+ var caf = window.cancelAnimationFrame
+ || window.cancelRequestAnimationFrame
+ || window.mozCancelAnimationFrame
+ || window.mozCancelRequestAnimationFrame
+ || window.webkitCancelAnimationFrame
+ || window.webkitCancelRequestAnimationFrame
+ || window.msCancelAnimationFrame
+ || window.msCancelRequestAnimationFrame
+ || function(id) { window.clearTimeout(id); };
+
+ /**
+ * Creates a fresh
+ * FastDom instance.
+ *
+ * @constructor
+ */
+ function FastDom() {
+ this.lastId = 0;
+ this.jobs = {};
+ this.mode = null;
+ this.pending = false;
+ this.queue = {
+ read: [],
+ write: []
+ };
+ }
+
+ /**
+ * Adds a job to
+ * the read queue.
+ *
+ * @param {Function} fn
+ * @api public
+ */
+ FastDom.prototype.read = function(fn, ctx) {
+ var job = this.add('read', fn, ctx);
+ this.queue.read.push(job.id);
+ this.request('read');
+ return job.id;
+ };
+
+ /**
+ * Adds a job to
+ * the write queue.
+ *
+ * @param {Function} fn
+ * @api public
+ */
+ FastDom.prototype.write = function(fn, ctx) {
+ var job = this.add('write', fn, ctx);
+ this.queue.write.push(job.id);
+ this.request('write');
+ return job.id;
+ };
+
+ /**
+ * Removes a job from
+ * the 'reads' queue.
+ *
+ * @param {Number} id
+ * @api public
+ */
+ FastDom.prototype.clear = function(id) {
+ var job = this.jobs[id];
+ if (!job) return;
+
+ // Clear reference
+ delete this.jobs[id];
+
+ // Defer jobs are cleared differently
+ if (job.type === 'defer') {
+ caf(job.timer);
+ return;
+ }
+
+ var list = this.queue[job.type];
+ var index = list.indexOf(id);
+ if (~index) list.splice(index, 1);
+ };
+
+ /**
+ * Makes the decision as to
+ * whether a the frame needs
+ * to be scheduled.
+ *
+ * @param {String} type
+ * @api private
+ */
+ FastDom.prototype.request = function(type) {
+ var mode = this.mode;
+ var self = this;
+
+ // If we are currently writing, we don't
+ // need to scedule a new frame as this
+ // job will be emptied from the write queue
+ if (mode === 'writing' && type === 'write') return;
+
+ // If we are reading we don't need to schedule
+ // a new frame as this read will be emptied
+ // in the currently active read queue
+ if (mode === 'reading' && type === 'read') return;
+
+ // If we are reading we don't need to schedule
+ // a new frame and this write job will be run
+ // after the read queue has been emptied in the
+ // currently active frame.
+ if (mode === 'reading' && type === 'write') return;
+
+ // If there is already a frame
+ // scheduled, don't schedule another one
+ if (this.pending) return;
+
+ // Schedule frame (preserving context)
+ raf(function() { self.frame(); });
+
+ // Set flag to indicate
+ // a frame has been scheduled
+ this.pending = true;
+ };
+
+ /**
+ * Generates a unique
+ * id for a job.
+ *
+ * @return {Number}
+ * @api private
+ */
+ FastDom.prototype.uniqueId = function() {
+ return ++this.lastId;
+ };
+
+ /**
+ * Calls each job in
+ * the list passed.
+ *
+ * If a context has been
+ * stored on the function
+ * then it is used, else the
+ * current `this` is used.
+ *
+ * @param {Array} list
+ * @api private
+ */
+ FastDom.prototype.flush = function(list) {
+ var id;
+ while (id = list.shift()) {
+ this.run(this.jobs[id]);
+ }
+ };
+
+ /**
+ * Runs any read jobs followed
+ * by any write jobs.
+ *
+ * @api private
+ */
+ FastDom.prototype.frame = function() {
+
+ // Set the pending flag to
+ // false so that any new requests
+ // that come in will schedule a new frame
+ this.pending = false;
+
+ // Set the mode to 'reading',
+ // then empty all read jobs
+ this.mode = 'reading';
+ this.flush(this.queue.read);
+
+ // Set the mode to 'writing'
+ // then empty all write jobs
+ this.mode = 'writing';
+ this.flush(this.queue.write);
+
+ this.mode = null;
+ };
+
+ /**
+ * Defers the given job
+ * by the number of frames
+ * specified.
+ *
+ * @param {Number} frames
+ * @param {Function} fn
+ * @api public
+ */
+ FastDom.prototype.defer = function(frames, fn, ctx) {
+ if (frames < 0) return;
+ var job = this.add('defer', fn, ctx);
+ var self = this;
+
+ (function wrapped() {
+ if (!(frames--)) {
+ self.run(job);
+ return;
+ }
+
+ job.timer = raf(wrapped);
+ })();
+
+ return job.id;
+ };
+
+ /**
+ * Adds a new job to
+ * the given queue.
+ *
+ * @param {Array} list
+ * @param {Function} fn
+ * @param {Object} ctx
+ * @returns {Number} id
+ * @api private
+ */
+ FastDom.prototype.add = function(type, fn, ctx) {
+ var id = this.uniqueId();
+ return this.jobs[id] = {
+ id: id,
+ fn: fn,
+ ctx: ctx,
+ type: type
+ };
+ };
+
+ /**
+ * Called when a callback errors.
+ * Overwrite this if you don't
+ * want errors inside your jobs
+ * to fail silently.
+ *
+ * @param {Error}
+ */
+ FastDom.prototype.onError = function(){};
+
+ /**
+ * Runs a given job.
+ * @param {Object} job
+ * @api private
+ */
+ FastDom.prototype.run = function(job){
+ var ctx = job.ctx || this;
+
+ // Clear reference to the job
+ delete this.jobs[job.id];
+
+ // Call the job in
+ try { job.fn.call(ctx); } catch(e) {
+ this.onError(e);
+ }
+ };
+
+ // We only ever want there to be
+ // one instance of FastDom in an app
+ fastdom = fastdom || new FastDom();
+
+ /**
+ * Expose 'fastdom'
+ */
+
+ if (typeof module !== 'undefined' && module.exports) {
+ module.exports = fastdom;
+ } else if (typeof define === "function" && define.amd) {
+ define(function(){ return fastdom; });
+ } else {
+ window['fastdom'] = fastdom;
+ }
+
+})(window.fastdom);
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..e4ea0f3
--- /dev/null
+++ b/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "fastdom",
+ "description": "Eliminates layout thrashing by batching DOM read/write operations",
+ "version": "0.7.1",
+ "main": "index.js",
+ "scripts": {
+ "test": "./node_modules/.bin/mocha-phantomjs test/index.html"
+ },
+ "homepage": "https://github.com/wilsonpage/fastdom",
+ "author": {
+ "name": "Wilson Page",
+ "email": "wilsonpage@me.com"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/wilsonpage/fastdom.git"
+ },
+ "license": "MIT",
+ "devDependencies": {
+ "mocha": "~1.12.0",
+ "sinon": "~1.7.3",
+ "chai": "~1.7.2",
+ "mocha-phantomjs": "~3.1.2"
+ }
+}
diff --git a/test/index.html b/test/index.html
new file mode 100644
index 0000000..4257f62
--- /dev/null
+++ b/test/index.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Mocha Tests</title>
+ <link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
+</head>
+<body>
+ <div id="mocha"></div>
+ <script src="../node_modules/mocha/mocha.js"></script>
+ <script src="../node_modules/chai/chai.js"></script>
+ <script src="../node_modules/sinon/lib/sinon.js"></script>
+ <script src="../node_modules/sinon/lib/sinon/match.js"></script>
+ <script src="../node_modules/sinon/lib/sinon/spy.js"></script>
+ <script>mocha.setup('tdd')</script>
+ <script src="../index.js"></script>
+ <script src="setup.js"></script>
+ <script src="test.set.js"></script>
+ <script src="test.clear.js"></script>
+ <script src="test.defer.js"></script>
+ <script>
+ mocha.checkLeaks();
+
+ if (window.mochaPhantomJS) mochaPhantomJS.run();
+ else mocha.run();
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/test/setup.js b/test/setup.js
new file mode 100644
index 0000000..0844695
--- /dev/null
+++ b/test/setup.js
@@ -0,0 +1,18 @@
+
+// RequestAnimationFrame Polyfill
+var raf = window.requestAnimationFrame
+ || window.webkitRequestAnimationFrame
+ || window.mozRequestAnimationFrame
+ || function(cb) { window.setTimeout(cb, 1000 / 60); };
+
+// Make constructor
+var FastDom = fastdom.constructor;
+
+// Alias chai.assert
+var assert = chai.assert;
+
+function objectLength(object) {
+ var l = 0;
+ for (var key in object) l++;
+ return l;
+} \ No newline at end of file
diff --git a/test/test.clear.js b/test/test.clear.js
new file mode 100644
index 0000000..ee60f2e
--- /dev/null
+++ b/test/test.clear.js
@@ -0,0 +1,97 @@
+
+suite('Clear', function(){
+
+ test("Should not run 'read' job if cleared (sync)", function(done) {
+ var fastdom = new FastDom();
+ var read = sinon.spy();
+
+ var id = fastdom.read(read);
+ fastdom.clear(id);
+
+ raf(function() {
+ assert(!read.called);
+ done();
+ });
+ });
+
+ test("Should fail silently if job not found in queue", function(done) {
+ var fastdom = new FastDom();
+ var read = sinon.spy();
+ var read2 = sinon.spy();
+
+ var id = fastdom.read(read);
+ fastdom.clear(id);
+
+ raf(function() {
+ assert(!read2.called);
+ done();
+ });
+ });
+
+ test("Should not run 'write' job if cleared (async)", function(done) {
+ var fastdom = new FastDom();
+ var read = sinon.spy();
+ var write = sinon.spy();
+
+ var id = fastdom.write(write);
+ fastdom.read(function() {
+ fastdom.clear(id);
+
+ raf(function() {
+ assert(!read.called);
+ done();
+ });
+ });
+ });
+
+ test("Should not run 'write' job if cleared", function(done) {
+ var fastdom = new FastDom();
+ var write = sinon.spy();
+ var id = fastdom.write(write);
+
+ fastdom.clear(id);
+
+ raf(function() {
+ assert(!write.called);
+ done();
+ });
+ });
+
+ test("Should not run 'defer' job if cleared", function(done) {
+ var fastdom = new FastDom();
+ var write = sinon.spy();
+ var id = fastdom.defer(3, write);
+
+ fastdom.clear(id);
+
+ raf(function() {
+ raf(function() {
+ raf(function() {
+ raf(function() {
+ assert(!write.called);
+ done();
+ });
+ });
+ });
+ });
+ });
+
+ test("Should remove reference to the job if cleared", function(done) {
+ var fastdom = new FastDom();
+ var write = sinon.spy();
+ var id = fastdom.defer(2, write);
+
+ fastdom.clear(id);
+
+ raf(function() {
+ raf(function() {
+ raf(function() {
+ assert(!write.called);
+ assert(!fastdom.jobs[id]);
+ done();
+ });
+ });
+ });
+ });
+
+}); \ No newline at end of file
diff --git a/test/test.defer.js b/test/test.defer.js
new file mode 100644
index 0000000..c1ae179
--- /dev/null
+++ b/test/test.defer.js
@@ -0,0 +1,50 @@
+
+suite('defer', function(){
+
+ test("Should run the job after the specified number of frames", function(done) {
+ var fastdom = new FastDom();
+ var job = sinon.spy();
+
+ fastdom.defer(4, job);
+
+ raf(function() {
+ assert(!job.called);
+ raf(function() {
+ assert(!job.called);
+ raf(function() {
+ assert(!job.called);
+ raf(function() {
+ assert(job.called);
+ done();
+ });
+ });
+ });
+ });
+ });
+
+ test("Should call a deferred callback with the given context", function(done) {
+ var fastdom = new FastDom();
+ var cb = sinon.spy();
+ var ctx = { foo: 'bar' };
+
+ fastdom.defer(2, function() {
+ assert.equal(this.foo, 'bar');
+ done();
+ }, ctx);
+ });
+
+ test("Should remove the reference to the job once run", function(done) {
+ var fastdom = new FastDom();
+ var callback = sinon.spy();
+ var id = fastdom.defer(2, callback);
+
+ raf(function() {
+ raf(function() {
+ raf(function() {
+ assert(!fastdom.jobs[id]);
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/test/test.set.js b/test/test.set.js
new file mode 100644
index 0000000..a1dd83c
--- /dev/null
+++ b/test/test.set.js
@@ -0,0 +1,187 @@
+
+suite('Set', function() {
+
+ test("Should run reads before writes", function(done) {
+ var fastdom = new FastDom();
+
+ var read = sinon.spy(function() {
+ assert(!write.called);
+ });
+
+ var write = sinon.spy(function() {
+ assert(read.called);
+ done();
+ });
+
+ fastdom.read(read);
+ fastdom.write(write);
+ });
+
+ test("Should call all reads together, followed by all writes", function(done) {
+ var fastdom = new FastDom();
+ var read1 = sinon.spy();
+ var read2 = sinon.spy();
+ var write1 = sinon.spy();
+ var write2 = sinon.spy();
+
+ // Assign unsorted
+ fastdom.read(read1);
+ fastdom.write(write1);
+ fastdom.read(read2);
+ fastdom.write(write2);
+
+ // After the queue has been emptied
+ // check the callbacks were called
+ // in the correct order.
+ raf(function() {
+ assert(read1.calledBefore(read2));
+ assert(read2.calledBefore(write1));
+ assert(write1.calledBefore(write2));
+ done();
+ });
+ });
+
+ test("Should call a read in the same frame if scheduled inside a read callback", function(done) {
+ var fastdom = new FastDom();
+ var cb = sinon.spy();
+
+ fastdom.read(function() {
+
+ // Schedule a callback for *next* frame
+ raf(cb);
+
+ // Schedule a read callback
+ // that should be run in the
+ // current frame checking that
+ // the RAF callback has not
+ // yet been fired.
+ fastdom.read(function() {
+ assert(!cb.called);
+ done();
+ });
+ });
+ });
+
+ test("Should call a write in the same frame if scheduled inside a read callback", function(done) {
+ var fastdom = new FastDom();
+ var cb = sinon.spy();
+
+ fastdom.read(function() {
+
+ // Schedule a callback for *next* frame
+ raf(cb);
+
+ // Schedule a read callback
+ // that should be run in the
+ // current frame checking that
+ // the RAF callback has not
+ // yet been fired.
+ fastdom.write(function() {
+ assert(!cb.called);
+ done();
+ });
+ });
+ });
+
+ test("Should call a read in the *next* frame if scheduled inside a write callback", function(done) {
+ var fastdom = new FastDom();
+ var cb = sinon.spy();
+
+ fastdom.write(function() {
+
+ // Schedule a callback for *next* frame
+ raf(cb);
+
+ // Schedule a write that should be
+ // called in the next frame, meaning
+ // the test callback should have already
+ // been called.
+ fastdom.read(function() {
+ assert(cb.called);
+ done();
+ });
+ });
+ });
+
+ test("Should call a 'read' callback with the given context", function(done) {
+ var fastdom = new FastDom();
+ var cb = sinon.spy();
+ var ctx = { foo: 'bar' };
+
+ fastdom.read(function() {
+ assert.equal(this.foo, 'bar');
+ done();
+ }, ctx);
+ });
+
+ test("Should call a 'write' callback with the given context", function(done) {
+ var fastdom = new FastDom();
+ var cb = sinon.spy();
+ var ctx = { foo: 'bar' };
+
+ fastdom.write(function() {
+ assert.equal(this.foo, 'bar');
+ done();
+ }, ctx);
+ });
+
+ test("Should have empty job hash when batch complete", function(done) {
+ var fastdom = new FastDom();
+
+ fastdom.read(function(){});
+ fastdom.read(function(){});
+ fastdom.write(function(){});
+ fastdom.write(function(){});
+
+ // Check there are four jobs stored
+ assert.equal(objectLength(fastdom.jobs), 4);
+
+ raf(function() {
+ assert.equal(objectLength(fastdom.jobs), 0);
+ done();
+ });
+ });
+
+ test("Should maintain correct context if single method is registered twice", function(done) {
+ var fastdom = new FastDom();
+ var ctx1 = { foo: 'bar' };
+ var ctx2 = { bar: 'baz' };
+
+ function shared(){}
+
+ var spy1 = sinon.spy(shared);
+ var spy2 = sinon.spy(shared);
+
+ fastdom.read(spy1, ctx1);
+ fastdom.read(spy2, ctx2);
+
+ raf(function() {
+ assert(spy1.calledOn(ctx1));
+ assert(spy2.calledOn(ctx2));
+ done();
+ });
+ });
+
+ test("Should call a registered onError handler when an error is thrown inside a job", function(done) {
+ var fastdom = new FastDom();
+ var err1 = { some: 'error1' };
+ var err2 = { some: 'error2' };
+
+ fastdom.onError = sinon.spy();
+
+ fastdom.read(function() {
+ throw err1;
+ });
+
+ fastdom.write(function() {
+ throw err2;
+ });
+
+ raf(function() {
+ assert(fastdom.onError.calledTwice);
+ assert(fastdom.onError.getCall(0).calledWith(err1));
+ assert(fastdom.onError.getCall(1).calledWith(err2));
+ done();
+ });
+ });
+}); \ No newline at end of file