From fa6a8ea57e2fb13c6785e9644678c6f990a9835b Mon Sep 17 00:00:00 2001 From: Seth Kinast Date: Fri, 17 Apr 2015 11:25:34 -0700 Subject: [PATCH] Don't require a key for `{@select}` Also allowed `type` to be set on the select instead of each truth test. If `type` is set both places, the value on the truth test will take precedence. Fixed coercion of undefined values. Previously, if a value was undefined it would not be coerced to the specified type. --- lib/dust-helpers.js | 104 ++++++++++++++----------- test/jasmine-test/spec/helpersTests.js | 41 ++++++---- 2 files changed, 82 insertions(+), 63 deletions(-) diff --git a/lib/dust-helpers.js b/lib/dust-helpers.js index 3f0d1f0..3a901e5 100644 --- a/lib/dust-helpers.js +++ b/lib/dust-helpers.js @@ -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); } @@ -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) { @@ -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; @@ -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 { @@ -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; }, diff --git a/test/jasmine-test/spec/helpersTests.js b/test/jasmine-test/spec/helpersTests.js index a2c64b0..0cd1a49 100644 --- a/test/jasmine-test/spec/helpersTests.js +++ b/test/jasmine-test/spec/helpersTests.js @@ -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", @@ -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" } ] }, @@ -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}"}
FOO
{/eq}', '{@eq value="{x}"}
BAR
{/eq}', '{/select}'].join("\n"), - context: { "test":"foo", "y": "foo", "x": "bar" }, + context: { "test":42, "y": 42, "x": "bar" }, expected: "
FOO
", message: "should test select helper with variable and type string with 2 conditions" }, @@ -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}\"}
FOO
{/eq}", - " {@eq value=\"{x}\"}
BAR
{/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", @@ -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}\"}
BAR
{/eq}", " {@eq value=\"{z}\"}
BAZ
{/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 .",