Fixes #176 - Marko v3: Migrate async taglib

This commit is contained in:
Patrick Steele-Idem 2016-02-11 15:00:58 -07:00
parent 90234c5bb1
commit 70716e2209
79 changed files with 471 additions and 448 deletions

View File

@ -143,6 +143,7 @@ exports.taglibLoader = require('./taglib-loader');
taglibLookup.registerTaglib(require.resolve('../taglibs/core/marko-taglib.json'));
taglibLookup.registerTaglib(require.resolve('../taglibs/layout/marko-taglib.json'));
taglibLookup.registerTaglib(require.resolve('../taglibs/html/marko-taglib.json'));
taglibLookup.registerTaglib(require.resolve('../taglibs/async/marko-taglib.json'));
/*
exports.Taglib = require('./Taglib');

View File

@ -1,32 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
function AsyncFragmentErrorNode(props) {
AsyncFragmentErrorNode.$super.call(this, 'async-fragment-error');
if (props) {
this.setProperties(props);
}
}
AsyncFragmentErrorNode.nodeType = 'element';
AsyncFragmentErrorNode.prototype = {
doGenerateCode: function (template) {
throw new Error('Illegal State. This node should have been removed');
}
};
module.exports = AsyncFragmentErrorNode;

View File

@ -1,32 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
function AsyncFragmentPlaceholderNode(props) {
AsyncFragmentPlaceholderNode.$super.call(this, 'async-fragment-placeholder');
if (props) {
this.setProperties(props);
}
}
AsyncFragmentPlaceholderNode.nodeType = 'element';
AsyncFragmentPlaceholderNode.prototype = {
doGenerateCode: function (template) {
throw new Error('Illegal State. This node should have been removed');
}
};
module.exports = AsyncFragmentPlaceholderNode;

View File

@ -1,32 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
function AsyncFragmentTimeoutNode(props) {
AsyncFragmentTimeoutNode.$super.call(this, 'async-fragment-timeout');
if (props) {
this.setProperties(props);
}
}
AsyncFragmentTimeoutNode.nodeType = 'element';
AsyncFragmentTimeoutNode.prototype = {
doGenerateCode: function (template) {
throw new Error('Illegal State. This node should have been removed');
}
};
module.exports = AsyncFragmentTimeoutNode;

View File

@ -1,16 +0,0 @@
'use strict';
module.exports = function transform(node, compiler, template) {
var asyncFragmentNode = node.parentNode;
if (!asyncFragmentNode) {
template.addError('<async-fragment-error> should be nested directly below an <async-fragment> tag.');
return;
}
// Remove the node from the tree
node.detach();
asyncFragmentNode.setProperty('errorMessage', node.getBodyContentExpression(template));
};

View File

@ -0,0 +1,25 @@
'use strict';
module.exports = function transform(el, context) {
var parentNode = el.parentNode;
if (parentNode.tagName !== 'async-fragment') {
context.addError(el, 'The <' + el.tagName + '> should be nested within an <async-fragment> tag.');
return;
}
var targetProp;
if (el.tagName === 'async-fragment-error') {
targetProp = 'renderError';
} else if (el.tagName === 'async-fragment-timeout') {
targetProp = 'renderTimeout';
} else if (el.tagName === 'async-fragment-placeholder') {
targetProp = 'renderPlaceholder';
}
var builder = context.builder;
parentNode.setAttributeValue(targetProp, builder.renderBodyFunction(el.body));
el.detach();
};

View File

@ -1,11 +0,0 @@
'use strict';
module.exports = function transform(node, compiler, template) {
var asyncFragmentNode = node.parentNode;
// Remove the node from the tree
node.detach();
asyncFragmentNode.setProperty('placeholder', node.getBodyContentExpression(template));
};

View File

@ -1,50 +1,103 @@
'use strict';
var varNameRegExp = /^[A-Za-z_][A-Za-z0-9_]*$/;
module.exports = function transform(node, compiler, template) {
var varName = node.getAttribute('var') || node.getAttribute('data-provider') || node.getAttribute('dependency');
var isObjectEmpty = require('raptor-util/isObjectEmpty');
module.exports = function transform(el, context) {
var varName = el.getAttributeValue('var');
if (varName) {
if (!varNameRegExp.test(varName)) {
node.addError('Invalid variable name of "' + varName + '"');
if (varName.type !== 'Literal' || typeof varName.value !== 'string') {
context.addError(el, 'The "var" attribute value should be a string');
return;
}
varName = varName.value;
if (!context.util.isValidJavaScriptIdentifier(varName)) {
context.addError(el, 'The "var" attribute value should be a valid JavaScript identifier');
return;
}
} else {
node.addError('Either "var" or "data-provider" is required');
context.addError(el, 'The "var" attribute is required');
return;
}
var attrs = el.getAttributes().concat([]);
var arg = {};
var builder = context.builder;
var argProps = [];
var propsToRemove = [];
var hasNameProp = false;
node.forEachProperty(function (name, value) {
if (name.startsWith('arg-')) {
var argName = name.substring('arg-'.length);
argProps.push(JSON.stringify(argName) + ': ' + value);
propsToRemove.push(name);
} else if (name === 'name') {
hasNameProp = true;
attrs.forEach((attr) => {
var attrName = attr.name;
if (attrName.startsWith('arg-')) {
let argName = attrName.substring('arg-'.length);
arg[argName] = attr.value;
el.removeAttribute(attrName);
}
});
if (!hasNameProp) {
var name = node.getAttribute('data-provider');
node.setProperty('_name', name);
var dataProviderAttr = el.getAttribute('data-provider');
if (!dataProviderAttr) {
context.addError(el, 'The "data-provider" attribute is required');
return;
}
propsToRemove.forEach(function (propName) {
node.removeProperty(propName);
});
var argString;
if (argProps.length) {
argString = '{' + argProps.join(', ') + '}';
if (dataProviderAttr.value == null) {
context.addError(el, 'A value is required for the "data-provider" attribute');
return;
}
var arg = node.getProperty('arg');
if (dataProviderAttr.value.type == 'Literal') {
context.addError(el, 'The "data-provider" attribute value should not be a literal ' + (typeof dataProviderAttr.value.value));
return;
}
var name = el.getAttributeValue('name');
if (name == null) {
el.setAttributeValue('_name', builder.literal(dataProviderAttr.rawValue));
}
if (el.hasAttribute('arg')) {
if (isObjectEmpty(arg)) {
arg = el.getAttributeValue('arg');
} else {
let mergeVar = context.addStaticVar('__merge', '__helpers.m');
arg = builder.functionCall(mergeVar, [
builder.literal(arg), // Input props from the attributes take precedence
el.getAttributeValue('arg')
]);
}
} else {
if (isObjectEmpty(arg)) {
arg = null;
} else {
arg = builder.literal(arg);
}
}
if (arg) {
var extendFuncName = template.getStaticHelperFunction('extend', 'xt');
argString = extendFuncName + '(' + arg + ', ' + argString + ')';
el.setAttributeValue('arg', arg);
}
if (argString) {
node.setProperty('arg', template.makeExpression(argString));
var timeoutMessage = el.getAttributeValue('timeout-message');
if (timeoutMessage) {
el.removeAttribute('timeout-message');
el.setAttributeValue('renderTimeout', builder.renderBodyFunction([
builder.text(timeoutMessage)
]));
}
var errorMessage = el.getAttributeValue('error-message');
if (errorMessage) {
el.removeAttribute('error-message');
el.setAttributeValue('renderError', builder.renderBodyFunction([
builder.text(errorMessage)
]));
}
var placeholder = el.getAttributeValue('placeholder');
if (placeholder) {
el.removeAttribute('placeholder');
el.setAttributeValue('renderPlaceholder', builder.renderBodyFunction([
builder.text(placeholder)
]));
}
};

View File

@ -77,7 +77,7 @@ module.exports = function render(input, out) {
var name = input.name || input._name;
var scope = input.scope || this;
function renderBody(err, data, timeoutMessage) {
function renderBody(err, data, renderTimeout) {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
@ -94,14 +94,14 @@ module.exports = function render(input, out) {
});
if (err) {
if (input.errorMessage) {
if (input.renderError) {
console.error('Async fragment (' + name + ') failed. Error:', (err.stack || err));
targetOut.write(input.errorMessage);
input.renderError(targetOut);
} else {
targetOut.error(err);
}
} else if (timeoutMessage) {
asyncOut.write(timeoutMessage);
} else if (renderTimeout) {
renderTimeout(asyncOut);
} else {
if (input.renderBody) {
input.renderBody(targetOut, data);
@ -135,7 +135,7 @@ module.exports = function render(input, out) {
if (!done) {
var timeout = input.timeout;
var timeoutMessage = input.timeoutMessage;
var renderTimeout = input.renderTimeout;
if (timeout == null) {
timeout = 10000;
@ -147,9 +147,9 @@ module.exports = function render(input, out) {
timeoutId = setTimeout(function() {
var message = 'Async fragment (' + name + ') timed out after ' + timeout + 'ms';
if (timeoutMessage) {
if (renderTimeout) {
logger.error(message);
renderBody(null, null, timeoutMessage);
renderBody(null, null, renderTimeout);
} else {
renderBody(new Error(message));
}

View File

@ -1,11 +0,0 @@
'use strict';
module.exports = function transform(node, compiler, template) {
var asyncFragmentNode = node.parentNode;
// Remove the node from the tree
node.detach();
asyncFragmentNode.setProperty('timeoutMessage', node.getBodyContentExpression(template));
};

View File

@ -1,76 +1,62 @@
{
"tags": {
"async-fragment": {
"renderer": "./async-fragment-tag",
"attributes": {
"data-provider": {
"type": "expression"
},
"arg": {
"type": "expression",
"preserve-name": true
},
"arg-*": {
"pattern": true,
"type": "string",
"preserve-name": true
},
"var": {
"type": "identifier"
},
"timeout": {
"type": "integer"
},
"method": {
"type": "string"
},
"timeout-message": {
"type": "string"
},
"error-message": {
"type": "string"
},
"name": {
"type": "string",
"description": "Name of async fragment (for debugging purposes only)"
},
"client-reorder": {
"type": "boolean",
"description": "Use JavaScript on client to move async fragment into the proper place."
},
"scope": {
"type": "expression"
},
"show-after": {
"type": "string"
},
"placeholder": {
"type": "string"
}
},
"vars": [
{
"name-from-attribute": "var"
}
],
"transformer": "./async-fragment-tag-transformer"
"<async-fragment>": {
"renderer": "./async-fragment-tag",
"@data-provider": "expression",
"@arg": {
"type": "expression",
"preserve-name": true
},
"async-fragments": {
"renderer": "./async-fragments-tag",
"attributes": {
}
"@arg-*": {
"pattern": true,
"type": "string",
"preserve-name": true
},
"async-fragment-placeholder": {
"node-class": "./AsyncFragmentPlaceholderNode",
"transformer": "./async-fragment-placeholder-tag-transformer"
"@var": "identifier",
"@method": "string",
"@timeout": "integer",
"@timeout-message": "string",
"@error-message": "string",
"@placeholder": "string",
"@renderTimeout": "function",
"@renderError": "function",
"@renderPlaceholder": "function",
"@name": {
"type": "string",
"description": "Name of async fragment"
},
"async-fragment-timeout": {
"node-class": "./AsyncFragmentTimeoutNode",
"transformer": "./async-fragment-timeout-tag-transformer"
"@_name": "string",
"@client-reorder": {
"type": "boolean",
"description": "Use JavaScript on client to move async fragment into the proper place."
},
"async-fragment-error": {
"node-class": "./AsyncFragmentErrorNode",
"transformer": "./async-fragment-error-tag-transformer"
}
"@scope": {
"type": "expression",
"description": "The value of 'this' when invoking the data provider function (N/A with promises)"
},
"@show-after": {
"type": "string"
},
"vars": [{
"name-from-attribute": "var"
}],
"transformer": "./async-fragment-tag-transformer"
},
"<async-fragments>": {
"renderer": "./async-fragments-tag"
},
"<async-fragment-placeholder>": {
"transformer": "./async-fragment-nested-tag-transformer"
},
"<async-fragment-timeout>": {
"transformer": "./async-fragment-nested-tag-transformer"
},
"<async-fragment-error>": {
"transformer": "./async-fragment-nested-tag-transformer"
}
}
}

View File

@ -4,20 +4,44 @@ chai.config.includeStack = true;
var path = require('path');
var marko = require('../');
var autotest = require('./autotest');
var fs = require('fs');
describe('render-async', function() {
var autoTestDir = path.join(__dirname, 'fixtures/render-async/autotest');
require('../node-require').install();
describe('render', function() {
var autoTestDir = path.join(__dirname, 'fixtures/async-render/autotest');
autotest.scanDir(
autoTestDir,
function run(dir) {
function run(dir, callback) {
var templatePath = path.join(dir, 'template.marko');
var mainPath = path.join(dir, 'test.js');
var template = marko.load(templatePath);
var main = require(mainPath);
var templateData = main.templateData || {};
var html = template.renderSync(templateData);
return html;
var main = fs.existsSync(mainPath) ? require(mainPath) : {};
var loadOptions = main && main.loadOptions;
if (main.checkError) {
var e;
try {
marko.load(templatePath, loadOptions);
} catch(_e) {
e = _e;
}
if (!e) {
throw new Error('Error expected');
}
main.checkError(e);
return callback(null, '$PASS$');
} else {
var template = marko.load(templatePath, loadOptions);
var templateData = main.templateData || {};
template.render(templateData, function(err, html) {
callback(err, html);
});
}
},
{
compareExtension: '.html'

View File

@ -1,56 +1,73 @@
'use strict';
var fs = require('fs');
var enabledTest = process.env.TEST;
var path = require('path');
var assert = require('assert');
function autoTest(name, dir, run, options) {
function autoTest(name, dir, run, options, done) {
var compareExtension = (options && options.compareExtension) || '.js';
var isJSON = compareExtension === '.json';
var actualPath = path.join(dir, 'actual' + compareExtension);
var expectedPath = path.join(dir, 'expected' + compareExtension);
function verify(actual) {
if (actual === '$PASS$') {
return;
}
var actualJSON = isJSON ? JSON.stringify(actual, null, 2) : null;
fs.writeFileSync(
actualPath,
isJSON ? actualJSON : actual,
{encoding: 'utf8'});
var expected;
try {
expected = fs.readFileSync(expectedPath, { encoding: 'utf8' });
} catch(e) {
expected = isJSON ? '"TBD"' : 'TBD';
fs.writeFileSync(expectedPath, expected, {encoding: 'utf8'});
}
var expectedJSON;
if (isJSON) {
expectedJSON = expected;
expected = JSON.parse(expectedJSON);
}
assert.deepEqual(
(isJSON ? JSON.parse(actualJSON) : actual),
expected,
'Unexpected output for "' + name + '":\nEXPECTED (' + expectedPath + '):\n---------\n' +
(isJSON ? expectedJSON : expected) +
'\n---------\nACTUAL (' + actualPath + '):\n---------\n' +
(isJSON ? actualJSON : actual) +
'\n---------');
}
try {
fs.unlinkSync(actualPath);
} catch(e) {}
if (done) {
// Async test
run(dir, function(err, actual) {
if (err) {
return done(err);
}
var actual = run(dir);
if (actual === '$PASS$') {
return;
verify(actual);
done();
});
} else {
let actual = run(dir);
verify(actual);
}
var actualJSON = isJSON ? JSON.stringify(actual, null, 2) : null;
fs.writeFileSync(
actualPath,
isJSON ? actualJSON : actual,
{encoding: 'utf8'});
var expected;
try {
expected = fs.readFileSync(expectedPath, { encoding: 'utf8' });
} catch(e) {
expected = isJSON ? '"TBD"' : 'TBD';
fs.writeFileSync(expectedPath, expected, {encoding: 'utf8'});
}
var expectedJSON;
if (isJSON) {
expectedJSON = expected;
expected = JSON.parse(expectedJSON);
}
assert.deepEqual(
(isJSON ? JSON.parse(actualJSON) : actual),
expected,
'Unexpected output for "' + name + '":\nEXPECTED (' + expectedPath + '):\n---------\n' +
(isJSON ? expectedJSON : expected) +
'\n---------\nACTUAL (' + actualPath + '):\n---------\n' +
(isJSON ? actualJSON : actual) +
'\n---------');
}
exports.scanDir = function(autoTestDir, run, options) {
@ -76,9 +93,17 @@ exports.scanDir = function(autoTestDir, run, options) {
var dir = path.join(autoTestDir, name);
itFunc(`[${name}] `, function() {
autoTest(name, dir, run, options);
});
if (run.length === 2) {
itFunc(`[${name}] `, function(done) {
autoTest(name, dir, run, options, done);
});
} else {
itFunc(`[${name}] `, function() {
autoTest(name, dir, run, options);
});
}
});
}

View File

@ -1,3 +1,3 @@
<async-fragment data-provider="data.nameDataProvider" var="name">
<async-fragment data-provider=data.nameDataProvider var="name">
Hello ${name}!
</async-fragment>

View File

@ -1,17 +0,0 @@
<ul>
<li for="userId in [0, 1, 2, 3]">
<async-fragment data-provider="data.userInfo" var="userInfo" arg-userId="$userId">
<ul>
<li>
<b>Name:</b> $userInfo.name
</li>
<li>
<b>Gender:</b> $userInfo.gender
</li>
<li>
<b>Occupation:</b> $userInfo.occupation
</li>
</ul>
</async-fragment>
</li>
</ul>

View File

@ -1,14 +0,0 @@
<async-fragment data-provider="data.outer" var="outer" client-reorder>
<h1>Outer</h1>
<async-fragment data-provider="data.inner1" var="inner1" client-reorder>
<h2>Inner 1</h2>
</async-fragment>
<async-fragment data-provider="data.inner2" var="inner2" client-reorder>
<h2>Inner 2</h2>
</async-fragment>
</async-fragment>
<async-fragments/>

View File

@ -1,7 +0,0 @@
<async-fragment data-provider="data.contextData" var="d1">
$d1.name
</async-fragment>
<async-fragment data-provider="data.sharedData" var="d2">
$d2.name
</async-fragment>

View File

@ -1,3 +0,0 @@
<async-fragment data-provider="data.userInfo" var="userInfo">
Hello $userInfo.name
</async-fragment>

View File

@ -1,33 +0,0 @@
exports.tests = [
{
templateData: {
userInfo: function() {
var deferred = require('raptor-promises').defer();
setTimeout(function() {
deferred.resolve({
name: 'John'
});
}, 200);
return deferred.promise;
}
}
},
{
templateData: {
userInfo: function() {
return {
name: 'John'
};
}
}
},
{
templateData: {
userInfo: function(arg, done) {
done(null, {
name: 'John'
});
}
}
}
];

View File

@ -1,7 +0,0 @@
<def function="asyncMacro(num)">
$num
</def>1
<async-fragment data-provider="data.D1" var="D1">
<invoke function="asyncMacro" num="2"/>
</async-fragment>
3

View File

@ -1,17 +0,0 @@
1
<async-fragment data-provider="data.D1" var="d1">
2
<async-fragment data-provider="data.D2" var="d2">
3
</async-fragment>
4
<async-fragment data-provider="data.D3" var="d3">
5
</async-fragment>
6
</async-fragment>
7
<async-fragment data-provider="data.D4" var="d4">
8
</async-fragment>
9

View File

@ -1,33 +0,0 @@
1
<async-fragment data-provider="data.D1" var="d1">
2
<async-fragment data-provider="data.D2" var="d2">
3
</async-fragment>
4
<async-fragment data-provider="data.D3" var="d3">
5
<async-fragment data-provider="data.D4" var="d4">
6
</async-fragment>
7
</async-fragment>
8
</async-fragment>
9
<async-fragment data-provider="data.D5" var="d5">
10
<async-fragment data-provider="data.D6" var="d6">
11
</async-fragment>
12
<async-fragment data-provider="data.D7" var="d7">
13
</async-fragment>
14
<async-fragment data-provider="data.D7" var="d7">
15
</async-fragment>
16
</async-fragment>
17

View File

@ -1,3 +0,0 @@
<async-fragment data-provider="data.promiseData" var="promiseData">
$promiseData
</async-fragment>

View File

@ -1,3 +0,0 @@
<async-fragment data-provider="data.userInfo" var="userInfo" timeout="100" timeout-message="Server is busy!">
Hello World
</async-fragment>

View File

@ -1,9 +0,0 @@
<async-fragment data-provider="data.userInfo" var="user" timeout="300">
<async-fragment-timeout>
1-A timeout has occurred!
</async-fragment-timeout>
1
</async-fragment>
<async-fragment data-provider="data.userInfo" var="user" timeout-message="2-A timeout has occurred!" timeout="300">
2
</async-fragment>

View File

@ -1,3 +0,0 @@
<var name="helper" value="data.helper"/>
A<invoke function="helper.beginAsync(out)"/>C

View File

@ -0,0 +1,17 @@
<ul>
<li for(userId in [0, 1, 2, 3])>
<async-fragment data-provider=data.userInfo var="userInfo" arg-userId=userId>
<ul>
<li>
<b>Name:</b> ${userInfo.name}
</li>
<li>
<b>Gender:</b> ${userInfo.gender}
</li>
<li>
<b>Occupation:</b> ${userInfo.occupation}
</li>
</ul>
</async-fragment>
</li>
</ul>

View File

@ -0,0 +1,14 @@
<async-fragment data-provider=data.outer var="outer" client-reorder>
<h1>Outer</h1>
<async-fragment data-provider=data.inner1 var="inner1" client-reorder>
<h2>Inner 1</h2>
</async-fragment>
<async-fragment data-provider=data.inner2 var="inner2" client-reorder>
<h2>Inner 2</h2>
</async-fragment>
</async-fragment>
<async-fragments/>

View File

@ -0,0 +1,7 @@
<async-fragment data-provider=data.contextData var="d1">
${d1.name}
</async-fragment>
<async-fragment data-provider=data.sharedData var="d2">
${d2.name}
</async-fragment>

View File

@ -0,0 +1,7 @@
---
BEFORE
<async-fragment data-provider=data.testDataProvider var="testData" error-message="something went wrong!">
Success!
</async-fragment>
AFTER
---

View File

@ -0,0 +1 @@
BEFORE something went wrong! AFTER

View File

@ -1,9 +1,11 @@
---
BEFORE
<async-fragment data-provider="data.testDataProvider" var="testData">
<async-fragment data-provider=data.testDataProvider var="testData">
Success!
<async-fragment-error>
something went wrong!
</async-fragment-error>
</async-fragment>
AFTER
AFTER
---

View File

@ -0,0 +1,8 @@
exports.templateData = {
testDataProvider: function(done) {
setTimeout(function() {
var err = new Error('Something went wrong!');
done(err, null);
}, 200);
}
};

View File

@ -0,0 +1,3 @@
<async-fragment data-provider=data.userInfo var="userInfo">
Hello ${userInfo.name}
</async-fragment>

View File

@ -0,0 +1,7 @@
exports.templateData = {
userInfo: function(arg, done) {
done(null, {
name: 'John'
});
}
};

View File

@ -0,0 +1,3 @@
<async-fragment data-provider=data.userInfo var="userInfo">
Hello ${userInfo.name}
</async-fragment>

View File

@ -0,0 +1,11 @@
exports.templateData = {
userInfo: function() {
var deferred = require('raptor-promises').defer();
setTimeout(function() {
deferred.resolve({
name: 'John'
});
}, 200);
return deferred.promise;
}
};

View File

@ -0,0 +1,3 @@
<async-fragment data-provider=data.userInfo var="userInfo">
Hello ${userInfo.name}
</async-fragment>

View File

@ -0,0 +1,7 @@
exports.templateData = {
userInfo: function() {
return {
name: 'John'
};
}
};

View File

@ -0,0 +1,9 @@
---
<macro asyncMacro(num)>
${num}
</macro>1
<async-fragment data-provider=data.D1 var="D1">
<asyncMacro num=2/>
</async-fragment>
3
---

View File

@ -0,0 +1,19 @@
---
1
<async-fragment data-provider=data.D1 var="d1">
2
<async-fragment data-provider=data.D2 var="d2">
3
</async-fragment>
4
<async-fragment data-provider=data.D3 var="d3">
5
</async-fragment>
6
</async-fragment>
7
<async-fragment data-provider=data.D4 var="d4">
8
</async-fragment>
9
---

View File

@ -0,0 +1,35 @@
---
1
<async-fragment data-provider=data.D1 var="d1">
2
<async-fragment data-provider=data.D2 var="d2">
3
</async-fragment>
4
<async-fragment data-provider=data.D3 var="d3">
5
<async-fragment data-provider=data.D4 var="d4">
6
</async-fragment>
7
</async-fragment>
8
</async-fragment>
9
<async-fragment data-provider=data.D5 var="d5">
10
<async-fragment data-provider=data.D6 var="d6">
11
</async-fragment>
12
<async-fragment data-provider=data.D7 var="d7">
13
</async-fragment>
14
<async-fragment data-provider=data.D7 var="d7">
15
</async-fragment>
16
</async-fragment>
17
---

View File

@ -1,7 +1,7 @@
<async-fragment data-provider="data.promiseData" var="promiseData">
<async-fragment data-provider=data.promiseData var="promiseData">
BEFORE
<if test="true">
$promiseData.noprop.value
<if(true)>
${promiseData.noprop.value}
</if>
AFTER
<async-fragment-error>

View File

@ -1,7 +1,7 @@
<async-fragment data-provider="data.promiseData" var="promiseData">
<async-fragment data-provider=data.promiseData var="promiseData">
BEFORE
<if test="true">
$promiseData.noprop.value
<if(true)>
${promiseData.noprop.value}
</if>
AFTER
<async-fragment-error>

View File

@ -0,0 +1,3 @@
<async-fragment data-provider=data.promiseData var="promiseData">
${promiseData}
</async-fragment>

View File

@ -0,0 +1,3 @@
<async-fragment data-provider=data.userInfo var="userInfo" timeout=100 timeout-message="Server is busy!">
Hello World
</async-fragment>

View File

@ -0,0 +1,9 @@
<async-fragment data-provider=data.userInfo var="user" timeout=300>
<async-fragment-timeout>
1-A timeout has occurred!
</async-fragment-timeout>
1
</async-fragment>
<async-fragment data-provider=data.userInfo var="user" timeout-message="2-A timeout has occurred!" timeout=300>
2
</async-fragment>

View File

@ -0,0 +1,3 @@
---
A<invoke data.beginAsync(out)/>C
---

View File

@ -0,0 +1,9 @@
exports.templateData = {
beginAsync: function(out) {
var asyncOut = out.beginAsync();
setTimeout(function() {
asyncOut.write('B');
asyncOut.end();
}, 20);
}
};

View File

@ -0,0 +1,3 @@
function renderBody(out) {
out.w("Hello World!");
}

View File

@ -0,0 +1,7 @@
'use strict';
module.exports = function(builder) {
return builder.renderBodyFunction([
builder.text(builder.literal('Hello World!'))
]);
};