diff options
Diffstat (limited to 'test/fastdom-test.js')
-rw-r--r-- | test/fastdom-test.js | 389 |
1 files changed, 389 insertions, 0 deletions
diff --git a/test/fastdom-test.js b/test/fastdom-test.js new file mode 100644 index 0000000..f28dff6 --- /dev/null +++ b/test/fastdom-test.js @@ -0,0 +1,389 @@ +/*jshint maxlen:false*/ +/*global suite, setup, teardown, test, assert, sinon, fastdomSandbox, fastdomPromised*/ + +suite('fastdom', function() { + var raf = window.requestAnimationFrame; + var fastdom; + + setup(function() { + fastdom = new window.fastdom.constructor(); + }); + + test('it runs reads before writes', function(done) { + var read = sinon.spy(function() { + assert(!write.called); + }); + + var write = sinon.spy(function() { + assert(read.called); + done(); + }); + + fastdom.measure(read); + fastdom.mutate(write); + }); + + test('it calls all reads together, followed by all writes', function(done) { + var read1 = sinon.spy(); + var read2 = sinon.spy(); + var write1 = sinon.spy(); + var write2 = sinon.spy(); + + // Assign unsorted + fastdom.measure(read1); + fastdom.mutate(write1); + fastdom.measure(read2); + fastdom.mutate(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('it calls a read in the same frame if scheduled inside a read callback', function(done) { + var cb = sinon.spy(); + + fastdom.measure(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.measure(function() { + assert(!cb.called); + done(); + }, this); + }, this); + }); + + test('it calls a write in the same frame if scheduled inside a read callback', function(done) { + var cb = sinon.spy(); + + fastdom.measure(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.mutate(function() { + assert(!cb.called); + done(); + }, this); + }, this); + }); + + test('it calls a read in the *next* frame if scheduled inside a write callback', function(done) { + var cb = sinon.spy(); + + fastdom.mutate(function() { + + // Schedule a callback for *next* frame + raf(cb); + + // Schedule a read that should be + // called in the next frame, meaning + // the test callback should have already + // been called. + fastdom.measure(function() { + assert(cb.called); + done(); + }, this); + }, this); + }); + + test('it does not request a new frame when a write is requested inside a nested read', function(done) { + var callback = sinon.spy(); + + fastdom.mutate(function() { + fastdom.measure(function() { + + // Schedule a callback for *next* frame + raf(callback); + + // Schedule a read callback + // that should be run in the + // current frame checking that + // the RAF callback has not + // yet been fired. + fastdom.mutate(function() { + assert(!callback.called); + done(); + }); + }); + }); + }); + + test('it schedules a new frame when a read is requested in a nested write', function(done) { + fastdom.raf = sinon.spy(fastdom, 'raf'); + + fastdom.measure(function() { + fastdom.mutate(function() { + fastdom.measure(function(){ + + // Should have scheduled a new frame + assert(fastdom.raf.calledTwice); + done(); + }); + }); + }); + }); + + test('it runs nested reads in the same frame', function(done) { + sinon.spy(fastdom, 'raf'); + + fastdom.measure(function() { + fastdom.measure(function() { + fastdom.measure(function() { + fastdom.measure(function() { + + // Should not have scheduled a new frame + sinon.assert.calledOnce(fastdom.raf); + done(); + }); + }); + }); + }); + }); + + test('it runs nested writes in the same frame', function(done) { + fastdom.raf = sinon.spy(fastdom, 'raf'); + + fastdom.mutate(function() { + fastdom.mutate(function() { + fastdom.mutate(function() { + fastdom.mutate(function() { + + // Should not have scheduled a new frame + sinon.assert.calledOnce(fastdom.raf); + done(); + }); + }); + }); + }); + }); + + test('it calls a "read" callback with the given context', function(done) { + fastdom.measure(function() { + assert.equal(this.foo, 'bar'); + done(); + }, { foo: 'bar' }); + }); + + test('it calls a "write" callback with the given context', function(done) { + fastdom.mutate(function() { + assert.equal(this.foo, 'bar'); + done(); + }, { foo: 'bar' }); + }); + + test('it has an empty job hash when batch complete', function(done) { + var ran = 0; + + fastdom.measure(function(){ ran += 1; }); + fastdom.measure(function(){ ran += 2; }); + fastdom.mutate(function(){ ran += 4; }); + fastdom.mutate(function(){ ran += 8; }); + + // Check there are four jobs stored + assert.equal(ran, 0); + + raf(function() { + assert.equal(ran, 15); + done(); + }); + }); + + test('it maintains correct context if single method is registered twice', function(done) { + var ctx1 = { foo: 'bar' }; + var ctx2 = { bar: 'baz' }; + + function shared() {} + + var spy1 = sinon.spy(shared); + var spy2 = sinon.spy(shared); + + fastdom.measure(spy1, ctx1); + fastdom.measure(spy2, ctx2); + + raf(function() { + assert(spy1.calledOn(ctx1)); + assert(spy2.calledOn(ctx2)); + done(); + }); + }); + + test('it runs .catch() handler on error if one has been registered', function(done) { + fastdom.catch = sinon.spy(); + + fastdom.measure(function() { throw 'err1'; }); + fastdom.mutate(function() { throw 'err2'; }); + + raf(function() { + raf(function() { + assert(fastdom.catch.calledTwice, 'twice'); + assert(fastdom.catch.getCall(0).calledWith('err1'), 'bla'); + assert(fastdom.catch.getCall(1).calledWith('err2'), 'bl2'); + done(); + }); + }); + }); + + suite('exceptions', function() { + + // temporarily disable mocha error detection + setup(function() { + this.onerror = window.onerror; + window.onerror = null; + }); + + // re-enable mocha error detection + teardown(function() { + window.onerror = this.onerror; + }); + + test('it flushes remaining tasks in next frame if prior task throws', function(done) { + var spy = sinon.spy(); + + fastdom.measure(function() { throw new Error('error'); }); + fastdom.measure(spy); + + raf(function() { + sinon.assert.notCalled(spy); + raf(function() { + sinon.assert.calledOnce(spy); + done(); + }); + }); + }); + }); + + test('it stops rAF loop once frame queue is empty', function(done) { + var callback = sinon.spy(); + + sinon.spy(fastdom, 'raf'); + fastdom.measure(callback); + + raf(function() { + assert(callback.called); + assert(fastdom.raf.calledOnce); + done(); + }); + }); + + suite('clear', function() { + test('it does not run "read" job if cleared (sync)', function(done) { + var read = sinon.spy(); + var id = fastdom.measure(read); + fastdom.clear(id); + + raf(function() { + raf(function() { + assert(!read.called); + done(); + }); + }); + }); + + test('it fails silently if job not found in queue', function(done) { + var read = sinon.spy(); + var read2 = sinon.spy(); + + var id = fastdom.measure(read); + fastdom.clear(id); + + raf(function() { + assert(!read2.called); + done(); + }); + }); + + test('it does not run "write" job if cleared (async)', function(done) { + var read = sinon.spy(); + var write = sinon.spy(); + + var id = fastdom.mutate(write); + fastdom.measure(function() { + fastdom.clear(id); + + raf(function() { + assert(!read.called); + done(); + }); + }); + }); + + test('it does not run "write" job if cleared', function(done) { + var write = sinon.spy(); + var id = fastdom.mutate(write); + + fastdom.clear(id); + + raf(function() { + assert(!write.called); + done(); + }); + }); + + test('it removes reference to the job if cleared', function(done) { + var write = sinon.spy(); + var id = fastdom.mutate(2, write); + + fastdom.clear(id); + + raf(function() { + raf(function() { + raf(function() { + assert(!write.called); + done(); + }); + }); + }); + }); + }); + + suite('FastDom#extend()', function() { + test('it has the properties of given object', function() { + var fastdom2 = fastdom.extend({ prop: 'foo' }); + assert.equal(fastdom2.prop, 'foo'); + }); + + test('it can extend an extension', function() { + var fastdom2 = fastdom.extend({ prop: 'foo' }); + var fastdom3 = fastdom2.extend({ prop: 'bar' }); + + assert.equal(fastdom2.prop, 'foo'); + assert.equal(fastdom3.prop, 'bar'); + + assert.equal(fastdom2.fastdom, fastdom); + assert.equal(fastdom3.fastdom, fastdom2); + }); + + test('it throws if argument is not object', function() { + assert.throws(function() { + fastdom.extend(); + }); + + assert.throws(function() { + fastdom.extend('oopsie'); + }); + + assert.throws(function() { + fastdom.extend(999); + }); + }); + }); +}); |