Skip to content

Commit

Permalink
Merge pull request #590 from sethkinast/rhino
Browse files Browse the repository at this point in the history
Add deep resolution of Thenables in context
  • Loading branch information
prashn64 committed Mar 26, 2015
2 parents c440dd0 + ebb9985 commit 2141106
Show file tree
Hide file tree
Showing 12 changed files with 371 additions and 212 deletions.
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ node_js:
- "0.12"
- "0.10"
- "0.8"
- "iojs-v1.4"
- "iojs"
env:
- TEST="all"
- TEST="node"
matrix:
exclude:
- node_js: "iojs-v1.4"
env: TEST="node"
- node_js: "iojs"
env: TEST="all"
- node_js: "0.12"
env: TEST="node"
env: TEST="all"
- node_js: "0.10"
env: TEST="node"
- node_js: "0.8"
Expand Down
16 changes: 12 additions & 4 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ module.exports = function(grunt) {
var commandList = [],
fs = require('fs'),
rhinoFolder = 'test/rhino/';
fs.readdirSync(__dirname + '/' + rhinoFolder + '/lib').forEach( function(rhinoJar) {
fs.readdirSync(__dirname + '/' + rhinoFolder + 'lib').forEach( function(rhinoJar) {
if(rhinoJar.indexOf('.jar') >= 0) {
commandList.push('java -jar ' + rhinoFolder + '/lib/' + rhinoJar + ' -f ' + rhinoFolder + '/rhinoTest.js');
commandList.push('java -jar ' + rhinoFolder + 'lib/' + rhinoJar + ' -f ' + rhinoFolder + 'rhinoTest.js');
}
});
return commandList.join(' && ');
Expand Down Expand Up @@ -137,15 +137,20 @@ module.exports = function(grunt) {
src: 'tmp/dust-full.min.js',
options: {
keepRunner: false,
specs: ['test/jasmine-test/spec/*.js']
display: 'short',
helpers: ['test/jasmine-test/spec/coreTests.js'],
specs: ['test/jasmine-test/spec/testHelpers.js', 'test/jasmine-test/spec/renderTestSpec.js'],
vendor: 'node_modules/ayepromise/ayepromise.js'
}
},
/*tests unminified code, mostly used for debugging by `grunt dev` task*/
testDev : {
src: 'tmp/dust-full.js',
options: {
keepRunner: false,
specs : '<%=jasmine.testProd.options.specs%>'
helpers: '<%=jasmine.testProd.options.helpers%>',
specs : '<%=jasmine.testProd.options.specs%>',
vendor: '<%=jasmine.testProd.options.vendor%>'
}
},
/*runs unit tests with jasmine and collects test coverage info via istanbul template
Expand All @@ -157,7 +162,10 @@ module.exports = function(grunt) {
src: 'tmp/dust-full.js',
options: {
keepRunner: false,
display: 'none',
helpers: '<%=jasmine.testProd.options.helpers%>',
specs : '<%=jasmine.testProd.options.specs%>',
vendor: '<%=jasmine.testProd.options.vendor%>',
template: require('grunt-template-jasmine-istanbul'),
templateOptions: {
coverage: 'tmp/coverage/coverage.json',
Expand Down
32 changes: 27 additions & 5 deletions lib/dust.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@
};

function Context(stack, global, blocks, templateName) {
this.stack = stack;
this.stack = stack;
this.global = global;
this.blocks = blocks;
this.templateName = templateName;
Expand All @@ -271,6 +271,18 @@
return new Context(new Stack(), global);
};

/**
* Factory function that creates a closure scope around a Thenable-callback.
* Returns a function that can be passed to a Thenable that will resume a
* Context lookup once the Thenable resolves with new data, adding that new
* data to the lookup stack.
*/
function getWithResolvedData(ctx, cur, down) {
return function(data) {
return ctx.push(data)._get(cur, down);
};
}

Context.wrap = function(context, name) {
if (context instanceof Context) {
return context;
Expand Down Expand Up @@ -315,6 +327,7 @@
var ctx = this.stack || {},
i = 1,
value, first, len, ctxThis, fn;

first = down[0];
len = down.length;

Expand Down Expand Up @@ -351,6 +364,10 @@
}

while (ctx && i < len) {
if (dust.isThenable(ctx)) {
// Bail early by returning a Thenable for the remainder of the search tree
return ctx.then(getWithResolvedData(this, cur, down.slice(i)));
}
ctxThis = ctx;
ctx = ctx[down[i]];
i++;
Expand Down Expand Up @@ -578,7 +595,7 @@
}

Chunk.prototype.write = function(data) {
var taps = this.taps;
var taps = this.taps;

if (taps) {
data = taps.go(data);
Expand Down Expand Up @@ -667,7 +684,7 @@
}
}

if (params) {
if (!dust.isEmptyObject(params)) {
context = context.push(params);
}

Expand Down Expand Up @@ -786,11 +803,16 @@
};

Chunk.prototype.helper = function(name, context, bodies, params) {
var chunk = this;
var chunk = this,
ret;
// handle invalid helpers, similar to invalid filters
if(dust.helpers[name]) {
try {
return dust.helpers[name](chunk, context, bodies, params);
ret = dust.helpers[name](chunk, context, bodies, params);
if (dust.isThenable(ret)) {
return this.await(ret, context, bodies);
}
return ret;
} catch(err) {
dust.log('Error in helper `' + name + '`: ' + err.message, ERROR);
return chunk.setError(err);
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,21 @@
"cli": "~0.6.5"
},
"devDependencies": {
"ayepromise": "~1.1.1",
"grunt": "~0.4.2",
"grunt-bump": "0.0.11",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-connect": "~0.5.0",
"grunt-contrib-copy": "~0.4.1",
"grunt-contrib-jasmine": "~0.5.2",
"grunt-contrib-jasmine": "~0.8.2",
"grunt-contrib-jshint": "~0.7.2",
"grunt-contrib-uglify": "~0.2.7",
"grunt-contrib-watch": "~0.5.3",
"grunt-gh-pages": "~0.9.0",
"grunt-jasmine-nodejs": "~1.0.2",
"grunt-shell": "~0.6.1",
"grunt-template-jasmine-istanbul": "~0.2.5",
"grunt-template-jasmine-istanbul": "~0.3.3",
"pegjs": "0.8.0"
},
"license": "MIT",
Expand Down
6 changes: 5 additions & 1 deletion test/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,11 @@ function testRender(unit, source, context, expected, options, baseContext, error
}
});
} catch(err) {
unit.contains(error, err.message || err);
if(error) {
unit.contains(error, err.message || err);
} else {
throw err;
}
}
unit.pass();
};
Expand Down
2 changes: 1 addition & 1 deletion test/jasmine-test/spec/cli/cliSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ describe('--version', function() {

function dustc(args, cb) {
var loc = path.join(BIN_DIR, 'dustc');
exec(loc + ' ' + args, { cwd: FIXTURE_DIR }, cb);
exec('node ' + loc + ' ' + args, { cwd: FIXTURE_DIR }, cb);
}

function fixture(file) {
Expand Down
105 changes: 82 additions & 23 deletions test/jasmine-test/spec/coreTests.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
if (typeof require !== 'undefined') {
ayepromise = require('ayepromise');
}
/**
* A naive fake-Promise that simply waits for callbacks
* to be bound by calling `.then` and then invokes
* one of the callbacks asynchronously.
* A naive Promise constructor that simply resolves or rejects its Promise based on what's passed
* @param err {*} Invokes the `error` callback with this value
* @param data {*} Invokes the `success` callback with this value, if `err` is not set
* @return {Object} a fake Promise with a `then` method
*/
function FakePromise(err, data) {
function then(success, failure) {
setTimeout(function() {
if(err) {
failure(err);
} else {
success(data);
}
}, 0);
function FalsePromise(err, data) {
var defer = ayepromise.defer();
if (err) {
defer.reject(new Error(err));
} else {
defer.resolve(data);
}

return {
"then": then
};
return defer.promise;
}

var coreTests = [
Expand Down Expand Up @@ -775,35 +770,99 @@ var coreTests = [
{
name: "thenable reference",
source: "Eventually {magic}!",
context: { "magic": new FakePromise(null, "magic") },
context: { "magic": new FalsePromise(null, "magic") },
expected: "Eventually magic!",
message: "should reserve an async chunk for a thenable reference"
},
{
name: "thenable deep reference",
source: "Eventually {magic.ally.delicious}!",
context: { "magic": new FalsePromise(null, {"ally": {"delicious": "Lucky Charms"} }) },
expected: "Eventually Lucky Charms!",
message: "should deep-inspect a thenable reference"
},
{
name: "thenable deep reference that doesn't exist",
source: "Eventually {magic.ally.disappeared}!",
context: { "magic": new FalsePromise(null, {"ally": {"delicious": "Lucky Charms"} }) },
expected: "Eventually !",
message: "should deep-inspect a thenable reference but move on if it isn't there"
},
{
name: "thenable deep reference... this is just getting silly",
source: "Eventually {magic.ally.delicious}!",
context: { "magic": new FalsePromise(null, {"ally": {"delicious": new FalsePromise(null, "Lucky Charms")} }) },
expected: "Eventually Lucky Charms!",
message: "should deep-inspect a thenable reference recursively"
},
{
name: "thenable reference that fails",
source: "Eventually {magic.ally.delicious}!",
context: { "magic": new FalsePromise("cereal gone") },
expected: "Eventually !",
message: "should inspect a thenable reference but move on if it fails"
},
{
name: "thenable deep reference that fails",
source: "Eventually {magic.ally.delicious}!",
context: { "magic": new FalsePromise(null, {"ally": {"delicious": new FalsePromise("cereal gone")} }) },
expected: "Eventually !",
message: "should deep-inspect a thenable reference but move on if it fails"
},
{
name: "thenable section",
source: "{#promise}Eventually {magic}!{/promise}",
context: { "promise": new FakePromise(null, {"magic": "magic"}) },
context: { "promise": new FalsePromise(null, {"magic": "magic"}) },
expected: "Eventually magic!",
message: "should reserve an async section for a thenable"
},
{
name: "thenable section from function",
source: "{#promise}Eventually {magic}!{/promise}",
context: { "promise": function() { return new FakePromise(null, {"magic": "magic"}); } },
context: { "promise": function() { return new FalsePromise(null, {"magic": "magic"}); } },
expected: "Eventually magic!",
message: "should reserve an async section for a thenable returned from a function"
},
{
name: "thenable deep section",
source: "Eventually my {#magic.ally}{delicious}{/magic.ally} will come",
context: { "magic": new FalsePromise(null, {"ally": {"delicious": new FalsePromise(null, "Lucky Charms")} }) },
expected: "Eventually my Lucky Charms will come",
message: "should reserve an async section for a deep-reference thenable"
},
{
name: "thenable deep section, traverse outside",
source: "Eventually my {#magic.ally}{prince} {delicious} {charms}{/magic.ally} will come",
base: { charms: new FalsePromise(null, "Charms") },
context: { "prince": "Prince", "magic": new FalsePromise(null, {"ally": {"delicious": new FalsePromise(null, "Lucky")} }) },
expected: "Eventually my Prince Lucky Charms will come",
message: "should reserve an async section for a deep-reference thenable and not blow the stack"
},
{
name: "thenable resolved by global helper",
source: '{@promise resolve="helper"}I am a big {.}!{/promise}',
context: {},
expected: "I am a big helper!",
message: "Dust helpers that return thenables are resolved in context"
},
{
name: "thenable rejected by global helper",
source: '{@promise reject="error"}I am a big helper!{:error}I am a big {.}!{/promise}',
context: {},
expected: "I am a big error!",
message: "Dust helpers that return thenables are rejected in context"
},
{
name: "thenable error",
source: "{promise}",
context: { "promise": new FakePromise("promise error") },
context: { "promise": new FalsePromise("promise error") },
log: "Unhandled promise rejection in `thenable error`",
message: "rejected thenable reference logs"
},
{
name: "thenable error with error block",
source: "{#promise}No magic{:error}{message}{/promise}",
context: { "promise": new FakePromise(new Error("promise error")) },
context: { "promise": new FalsePromise("promise error") },
expected: "promise error",
message: "rejected thenable renders error block"
}
Expand Down Expand Up @@ -1285,8 +1344,8 @@ var coreTests = [
'{>"{parentTemplate}"/} | additional parent output'].join("\n"),
context: { "loadTemplate": function(chunk, context, bodies, params)
{
var source = dust.testHelpers.tap(params.source, chunk, context),
name = dust.testHelpers.tap(params.name, chunk, context);
var source = context.resolve(params.source),
name = context.resolve(params.name);
dust.loadSource(dust.compile(source, name));
return chunk.write('');
},
Expand Down
Loading

0 comments on commit 2141106

Please sign in to comment.