Skip to content

Commit

Permalink
Merge pull request #133 from sethkinast/select-no-key
Browse files Browse the repository at this point in the history
Don't require a key for `{@select}`
  • Loading branch information
sethkinast committed Apr 18, 2015
2 parents 5b36bd5 + fa6a8ea commit dcfa99e
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 63 deletions.
104 changes: 57 additions & 47 deletions lib/dust-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,30 @@ function getSelectState(context) {
/**
* Adds a special __select__ key behind the head of the context stack. Used to maintain the state
* of {@select} blocks
* @param context {Context} add state to this Context
* @param opts {Object} add these properties to the state (`key` and `type`)
*/
function addSelectState(context, key) {
function addSelectState(context, opts) {
var head = context.stack.head,
newContext = context.rebase();
newContext = context.rebase(),
key;

if(context.stack && context.stack.tail) {
newContext.stack = context.stack.tail;
}

var state = {
isResolved: false,
isDeferredComplete: false,
deferreds: []
};

for(key in opts) {
state[key] = opts[key];
}

return newContext
.push({ "__select__": {
isResolved: false,
isDeferredComplete: false,
deferreds: [],
key: key
}
})
.push({ "__select__": state })
.push(head, context.stack.index, context.stack.of);
}

Expand Down Expand Up @@ -97,28 +104,28 @@ function truthTest(name, test) {
function filter(chunk, context, bodies, params, helperName, test) {
var body = bodies.block,
skip = bodies.else,
selectState = getSelectState(context),
key, value;

// Currently we first check for a key on the helper itself, then fall back to
// looking for a key on the {@select} that contains it. This is undocumented
// behavior that we may or may not support in the future. (If we stop supporting
// it, just switch the order of the test below to check the {@select} first.)
if (params.hasOwnProperty("key")) {
key = context.resolve(params.key);
} else if (selectState) {
selectState = getSelectState(context) || {},
key, value, type;

// Once one truth test in a select passes, short-circuit the rest of the tests
if (selectState.isResolved) {
return chunk;
}

// First check for a key on the helper itself, then look for a key on the {@select}
if (params.hasOwnProperty('key')) {
key = params.key;
} else if (selectState.hasOwnProperty('key')) {
key = selectState.key;
// Once one truth test in a select passes, short-circuit the rest of the tests
if (selectState.isResolved) {
return chunk;
}
} else {
log(helperName, "No key specified", "WARN");
return chunk;
}

key = coerce(key, params.type);
value = coerce(context.resolve(params.value), params.type);
type = params.type || selectState.type;

key = coerce(context.resolve(key), type);
value = coerce(context.resolve(params.value), type);

if (test(key, value)) {
if (selectState) {
Expand All @@ -134,16 +141,16 @@ function filter(chunk, context, bodies, params, helperName, test) {
}

function coerce(value, type) {
if (typeof value !== 'undefined' &&
typeof type !== 'undefined') {
switch (type) {
case 'number': return +value;
case 'string': return String(value);
case 'boolean':
value = (value === 'false' ? false : value);
return Boolean(value);
case 'date': return new Date(value);
}
if (type) {
type = type.toLowerCase();
}
switch (type) {
case 'number': return +value;
case 'string': return String(value);
case 'boolean':
value = (value === 'false' ? false : value);
return Boolean(value);
case 'date': return new Date(value);
}

return value;
Expand Down Expand Up @@ -275,7 +282,7 @@ var helpers = {
output = Math.round(output);
}
if (bodies && bodies.block) {
context = addSelectState(context, output);
context = addSelectState(context, { key: output });
chunk = chunk.render(bodies.block, context);
resolveSelectDeferreds(getSelectState(context));
} else {
Expand All @@ -291,22 +298,25 @@ var helpers = {
* Groups a set of truth tests and outputs the first one that passes.
* Also contains {@any} and {@none} blocks.
* @param key a value or reference to use as the left-hand side of comparisons
* @param type coerce all truth test keys without an explicit type to this type
*/
"select": function(chunk, context, bodies, params) {
var body = bodies.block,
key;
state = {};

if (params.hasOwnProperty("key")) {
key = context.resolve(params.key);
if (body) {
context = addSelectState(context, key);
chunk = chunk.render(body, context);
resolveSelectDeferreds(getSelectState(context));
} else {
log("select", "Missing body block", "WARN");
}
if (params.hasOwnProperty('key')) {
state.key = context.resolve(params.key);
}
if (params.hasOwnProperty('type')) {
state.type = params.type;
}

if (body) {
context = addSelectState(context, state);
chunk = chunk.render(body, context);
resolveSelectDeferreds(getSelectState(context));
} else {
log("select", "`key` is required", "ERROR");
log("select", "Missing body block", "WARN");
}
return chunk;
},
Expand Down
41 changes: 25 additions & 16 deletions test/jasmine-test/spec/helpersTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@
source: "{@eq key=x value=\"0\" type=\"string\"}equal{/eq}",
context: {x:0},
expected: "equal",
message: "eq helper should coerce falsy booleans"
message: "eq helper should coerce falsy values to string"
},
{
name: "eq helper without a body",
Expand All @@ -540,17 +540,17 @@
tests: [
{
name: "not eq helper true/notequal boolean case",
source: "{@ne key=\"true\" value=\"false\" type=\"boolean\"}not equal{/ne}",
source: "{@ne key=\"true\" value=\"false\" type=\"BOOLEAN\"}not equal{/ne}",
context: {},
expected: "not equal",
message: "not eq helper true/notequal boolean case"
message: "not eq helper true/notequal boolean case"
},
{
name: "not eq helper alse/equal boolean case",
source: "{@ne key=\"false\" value=\"false\" type=\"boolean\"}equal{/ne}",
source: "{@ne key=\"false\" value=\"false\" type=\"Boolean\"}equal{/ne}",
context: {},
expected: "",
message: "not eq helper alse/equal boolean case"
message: "not eq helper false/equal boolean case"
}
]
},
Expand Down Expand Up @@ -924,11 +924,11 @@
},
{
name: "select helper with variable and type string with 2 conditions",
source: ['{@select key=test}',
source: ['{@select key=test type="string"}',
'{@eq value="{y}"}<div>FOO</div>{/eq}',
'{@eq value="{x}"}<div>BAR</div>{/eq}',
'{/select}'].join("\n"),
context: { "test":"foo", "y": "foo", "x": "bar" },
context: { "test":42, "y": 42, "x": "bar" },
expected: "<div>FOO</div>",
message: "should test select helper with variable and type string with 2 conditions"
},
Expand All @@ -947,16 +947,15 @@
message: "should test select helper with variable and type string in a nested objects"
},
{
name: "select helper with missing key parameter and hence no output",
name: "select helper with no key parameter",
source: ["{#b}{@select}",
" {@eq value=\"{z}\"}<div>FOO</div>{/eq}",
" {@eq value=\"{x}\"}<div>BAR</div>{/eq}",
" {@eq key=x value=\"{z}\"}FOO{/eq}",
" {@eq key=x value=\"{x}\"}BAR{/eq}",
" {@none}foofoo{/none}",
"{/select}{/b}"].join("\n"),
context: { b : { z: "foo", x: "bar" } },
expected: "",
log: "{@select}: `key` is required",
message: "should test select helper with missing key in the context and hence no output"
expected: "BAR",
message: "should test select helper with no key"
},
{
name: "select helper with key not defined in the context",
Expand All @@ -970,15 +969,25 @@
message: "should test select helper with undefined key in the context"
},
{
name: "select helper wih key matching the default condition",
name: "select helper with key that matches no tests",
source: ["{#b}{@select key=\"{x}\"}",
" {@eq value=\"{y}\"}<div>BAR</div>{/eq}",
" {@eq value=\"{z}\"}<div>BAZ</div>{/eq}",
" {@none value=\"foo\"}foofoo{/none}",
" {@none}foofoo{/none}",
"{/select}{/b}"].join("\n"),
context: { b : { "x": "foo", "y": "bar", "z": "baz" } },
expected: "foofoo",
message: "should test select helper with key matching the default condition"
message: "should test select helper with key that matches no tests"
},
{
name: "select helper with undefined key coerced to boolean",
source: ['{@select key=not_there}',
'{@eq value="true" type="boolean"}all the messages{/eq}',
'{@eq value="false" type="boolean"}no messages{/eq}',
'{/select}'].join(''),
context: {},
expected: 'no messages',
message: 'should test select helper with undefined key coerced to boolean'
},
{
name: "select helper inside a array with .",
Expand Down

0 comments on commit dcfa99e

Please sign in to comment.