From 83c84f7833fb131a7f425f3f045c3172c6a80e95 Mon Sep 17 00:00:00 2001 From: austinkelleher Date: Fri, 11 Nov 2016 08:31:03 -0500 Subject: [PATCH 1/2] Fixes #251 - Output of render should be Promise compatible. --- runtime/OutMixins.js | 23 ++++++++++++ runtime/html/AsyncStream.js | 6 +-- test/AsyncStream-test.js | 37 +++++++++++++++++++ test/AsyncVDOMBuilder-test.js | 9 +++++ .../api/load-render-promise/template.marko | 1 + .../autotests/api/load-render-promise/test.js | 16 ++++++++ 6 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 test/autotests/api/load-render-promise/template.marko create mode 100644 test/autotests/api/load-render-promise/test.js diff --git a/runtime/OutMixins.js b/runtime/OutMixins.js index b0a99c417..2de621843 100644 --- a/runtime/OutMixins.js +++ b/runtime/OutMixins.js @@ -71,6 +71,29 @@ module.exports = { return this; }, + then: function(fn, fnErr) { + var self = this; + var promise = new Promise(function(resolve, reject) { + self.on('error', reject); + self.on('finish', function(data) { + try { + resolve(fn(data)); + } catch(err) { + reject(err); + } + }); + }); + + if (fnErr) { + promise = promise.catch(fnErr); + } + return promise; + }, + + catch: function(fnErr) { + return this.then(undefined, fnErr); + }, + appendTo: function(referenceEl) { var newNode = this.getNode(referenceEl.ownerDocument); dom.appendTo(newNode, referenceEl); diff --git a/runtime/html/AsyncStream.js b/runtime/html/AsyncStream.js index bd3946b49..d89815dff 100644 --- a/runtime/html/AsyncStream.js +++ b/runtime/html/AsyncStream.js @@ -221,7 +221,7 @@ var proto = AsyncStream.prototype = { if (state.writer.end) { state.writer.end(); } else { - state.events.emit('finish'); + state.events.emit('finish', this); } } } @@ -290,7 +290,7 @@ var proto = AsyncStream.prototype = { var state = this._state; if (event === 'finish' && state.finished) { - callback(); + callback(this); return this; } @@ -302,7 +302,7 @@ var proto = AsyncStream.prototype = { var state = this._state; if (event === 'finish' && state.finished) { - callback(); + callback(this); return this; } diff --git a/test/AsyncStream-test.js b/test/AsyncStream-test.js index 4ca8ebf46..2c132eaef 100644 --- a/test/AsyncStream-test.js +++ b/test/AsyncStream-test.js @@ -57,6 +57,19 @@ describe('async-writer' , function() { }); }); + it('should resolve promise upon finish', function() { + var out = new AsyncStream(); + + out.write('1'); + out.write('2'); + + return out.end().then((data) => { + const output = out.getOutput(); + expect(output).to.equal('12'); + expect(data.getOutput()).to.equal('12'); + }); + }); + it('should render a series of sync and async calls correctly', function(done) { var out = new AsyncStream(); out.write('1'); @@ -250,6 +263,30 @@ describe('async-writer' , function() { }); }); + it('should catch error in promise catch', function(done) { + const out = new AsyncStream(); + + let errors = []; + + out.on('error', function(e) { + errors.push(e); + }); + + out.write('1'); + + let asyncOut = out.beginAsync(); + setTimeout(function() { + asyncOut.error(new Error('test')); + }, 10); + + out.write('3'); + out.end().catch((err) => { + expect(errors.length).to.equal(1); + expect(err).to.be.an('error'); + done(); + }); + }); + it('should support chaining', function(done) { var errors = []; var out = new AsyncStream() diff --git a/test/AsyncVDOMBuilder-test.js b/test/AsyncVDOMBuilder-test.js index da7692e4d..3c8019bf4 100644 --- a/test/AsyncVDOMBuilder-test.js +++ b/test/AsyncVDOMBuilder-test.js @@ -41,6 +41,15 @@ it('async', function(done) { }); }); +it('promise', function(done) { + const out = new AsyncVDOMBuilder(); + out.element('div', {}, 0); + out.end().then((tree) => { + expect(tree.childNodes.length).to.equal(1); + done(); + }); +}); + it('async flush', function(done) { var out = new AsyncVDOMBuilder(); out.on('update', function(tree) { diff --git a/test/autotests/api/load-render-promise/template.marko b/test/autotests/api/load-render-promise/template.marko new file mode 100644 index 000000000..eabd502aa --- /dev/null +++ b/test/autotests/api/load-render-promise/template.marko @@ -0,0 +1 @@ +- Hello ${data.name}! \ No newline at end of file diff --git a/test/autotests/api/load-render-promise/test.js b/test/autotests/api/load-render-promise/test.js new file mode 100644 index 000000000..656f6e105 --- /dev/null +++ b/test/autotests/api/load-render-promise/test.js @@ -0,0 +1,16 @@ +'use strict'; + +const nodePath = require('path'); + +exports.check = function(marko, markoCompiler, expect, done) { + let template = marko.load(nodePath.join(__dirname, 'template.marko')); + + template.render({ + name: 'John' + }).then((out) => { + expect(out.getOutput()).to.equal('Hello John!'); + done(); + }).catch((err) => { + done(err); + }); +}; \ No newline at end of file From 56694c41a77a663fdc012336700e473592435ee7 Mon Sep 17 00:00:00 2001 From: austinkelleher Date: Sat, 12 Nov 2016 05:15:54 -0500 Subject: [PATCH 2/2] Add test for ensuring that out.error only drops into . --- test/AsyncStream-test.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/AsyncStream-test.js b/test/AsyncStream-test.js index 2c132eaef..946632605 100644 --- a/test/AsyncStream-test.js +++ b/test/AsyncStream-test.js @@ -287,6 +287,22 @@ describe('async-writer' , function() { }); }); + it('should catch error in promise catch if `error` listener only set inside mixin', function(done) { + const out = new AsyncStream(); + + out.catch((err) => { + expect(err).to.be.an('error'); + expect(out.getOutput()).to.equal('1'); + done(); + }).then((data) => { + throw new Error('Should not get here!'); + }); + + out.write('1'); + out.error(new Error('test')); + out.write('2'); + }); + it('should support chaining', function(done) { var errors = []; var out = new AsyncStream()