This commit is contained in:
ido 2015-09-03 10:14:51 +03:00
parent 6db983e7cc
commit b3fd044344
8 changed files with 113 additions and 87 deletions

View File

@ -293,9 +293,8 @@ function generateProps(node, context) {
res[1] = res.slice(1).join(':').trim();
return res;
}));
var styleArray = [];
_.forEach(styleParts, function (stylePart) {
styleArray.push(stringUtils.convertToCamelCase(stylePart[0]) + ' : ' + convertText(node, context, stylePart[1].trim()));
var styleArray = _.map(styleParts, function (stylePart) {
return stringUtils.convertToCamelCase(stylePart[0]) + ' : ' + convertText(node, context, stylePart[1].trim());
});
props[propKey] = '{' + styleArray.join(',') + '}';
} else if (propKey === classNameProp) {
@ -362,6 +361,33 @@ function hasNonSimpleChildren(node) {
});
}
/*
interface NodeConversionData {
innerScopeData: InnerScopeData;
repeatChildrenData: RepeatChildrenData;
ifData: IfData;
}
interface InnerScopeData {
scopeName: string;
// these are variables that were already in scope, unrelated to the ones declared in rt-inner-scope
innerMapping: {[alias: string]: any};
// these are variables declared in the rt-inner-scope attribute
outerMapping: {[alias: string]: any};
}
interface RepeatChildrenData {
itemAlias: string;
collectionExpression: string;
binds: string[];
fn();
}
interface IfData {
conditionExpression: string;
}
*/
/**
* @param node
* @param {Context} context
@ -377,41 +403,9 @@ function convertHtmlToReact(node, context) {
};
var data = {name: convertTagNameToConstructor(node.name, context)};
if (node.attribs[scopeAttr]) {
//data.scopeMapping = {};
data.scopeName = '';
// these are variables that were already in scope, unrelated to the ones declared in rt-scope
data.outerScopeMapping = {};
_.forEach(context.boundParams, function (boundParam) {
data.outerScopeMapping[boundParam] = boundParam;
});
// these are variables declared in the rt-scope attribute
data.innerScopeMapping = {};
_.forEach(node.attribs[scopeAttr].split(';'), function (scopePart) {
if (scopePart.trim().length === 0) {
return;
}
var scopeSubParts = scopePart.split(' as ');
if (scopeSubParts.length < 2) {
throw RTCodeError.build("invalid scope part '" + scopePart + "'", context, node);
}
var scopeName = scopeSubParts[1].trim();
validateJS(scopeName, node, context);
// this adds both parameters to the list of parameters passed further down
// the scope chain, as well as variables that are locally bound before any
// function call, as with the ones we generate for rt-scope.
stringUtils.addIfMissing(context.boundParams, scopeName);
data.scopeName += stringUtils.capitalize(scopeName);
data.innerScopeMapping[scopeName] = scopeSubParts[0].trim();
validateJS(data.innerScopeMapping[scopeName], node, context);
});
}
// Order matters. We need to add the item and itemIndex to context.boundParams before
// the rt-scope directive is processed, lest they are not passed to the child scopes
if (node.attribs[templateAttr]) {
var arr = node.attribs[templateAttr].split(' in ');
if (arr.length !== 2) {
@ -424,6 +418,44 @@ function convertHtmlToReact(node, context) {
stringUtils.addIfMissing(context.boundParams, data.item);
stringUtils.addIfMissing(context.boundParams, data.item + 'Index');
}
if (node.attribs[scopeAttr]) {
data.innerScope = {
scopeName: '',
innerMapping: {},
outerMapping: {}
};
_.forEach(context.boundParams, function (boundParam) {
data.innerScope.outerMapping[boundParam] = boundParam;
});
//_(node.attribs[scopeAttr]).split(';').invoke('trim').compact().forEach().value()
_.forEach(node.attribs[scopeAttr].split(';'), function (scopePart) {
if (scopePart.trim().length === 0) {
return;
}
var scopeSubParts = scopePart.split(' as ');
if (scopeSubParts.length < 2) {
throw RTCodeError.build("invalid scope part '" + scopePart + "'", context, node);
}
var alias = scopeSubParts[1].trim();
var value = scopeSubParts[0].trim();
validateJS(alias, node, context);
// this adds both parameters to the list of parameters passed further down
// the scope chain, as well as variables that are locally bound before any
// function call, as with the ones we generate for rt-scope.
stringUtils.addIfMissing(context.boundParams, alias);
data.innerScope.scopeName += stringUtils.capitalize(alias);
data.innerScope.innerMapping[alias] = value;
validateJS(data.innerScope.innerMapping[alias], node, context);
});
}
data.props = generateProps(node, context);
if (node.attribs[propsAttr]) {
if (data.props === '{}') {
@ -452,26 +484,25 @@ function convertHtmlToReact(node, context) {
data.body = shouldUseCreateElement(context) ? simpleTagTemplateCreateElement(data) : simpleTagTemplate(data);
}
if (node.attribs[scopeAttr]) {
var scopeVarDeclarations = _.map(data.innerScope.innerMapping, function (v, k) { return 'var ' + k + ' = ' + v + ';'; }).join('\n');
var functionBody = scopeVarDeclarations + 'return ' + data.body;
var generatedFuncName = generateInjectedFunc(context, 'scope' + data.innerScope.scopeName, functionBody, _.keys(data.innerScope.outerMapping));
data.body = generatedFuncName + '.apply(this, [' + _.values(data.innerScope.outerMapping).join(',') + '])';
}
// Order matters here. Each rt-repeat iteration wraps over the rt-scope, so
// the scope variables are evaluated in context of the current iteration.
if (node.attribs[templateAttr]) {
data.repeatFunction = generateInjectedFunc(context, 'repeat' + stringUtils.capitalize(data.item), 'return ' + data.body);
data.repeatBinds = ['this'].concat(_.reject(context.boundParams, function (param) {
return param === data.item || param === data.item + 'Index';
return param === data.item || param === data.item + 'Index' || data.innerScope && param in data.innerScope.innerMapping;
}));
data.body = repeatTemplate(data);
}
if (node.attribs[ifAttr]) {
data.body = ifTemplate(data);
}
if (node.attribs[scopeAttr]) {
var scopeVarDeclarations = _.reduce(data.innerScopeMapping, function (acc, rightHandSide, leftHandSide) {
var declaration = 'var ' + leftHandSide + ' = ' + rightHandSide + ';';
return acc + declaration;
}, '');
var functionBody = scopeVarDeclarations + 'return ' + data.body;
var generatedFuncName = generateInjectedFunc(context, 'scope' + data.scopeName, functionBody, _.keys(data.outerScopeMapping));
data.body = generatedFuncName + '.apply(this, [' + _.values(data.outerScopeMapping).join(',') + '])';
}
return data.body;
} else if (node.type === 'comment') {
return commentTemplate(node);

View File

@ -0,0 +1,8 @@
<div>
<div rt-repeat="pirate in [{first: 'Jack', last: 'Sparrow', photo: {url: 'jack.jpg'}}, {first: 'Unknown', last: 'Pirate'}]">
<h1>{pirate.first} {pirate.last}</h1>
<div rt-if="pirate.photo" rt-scope="pirate.photo.url as url">
<img src="{url}"/>
</div>
</div>
</div>

View File

@ -0,0 +1 @@
<div><div><h1>Jack Sparrow</h1><div><img src="jack.jpg"/></div></div><div><h1>Unknown Pirate</h1></div></div>

View File

@ -0,0 +1,5 @@
<div>
<div rt-repeat="pirate in [{name: 'Jack Sparrow', profile: {age: 99}}, {name: 'Someone Else', profile: {age: 98}}]" rt-scope="pirate.profile as profile" key="p-{pirateIndex}">
{pirateIndex}-{pirate.name}-{profile.age}
</div>
</div>

View File

@ -0,0 +1 @@
<div><div>0-Jack Sparrow-99</div><div>1-Someone Else-98</div></div>

View File

@ -0,0 +1,9 @@
<div>
<div rt-scope="15 as fifteen; 10 as ten">
<div rt-repeat="number in [111]">
<div rt-repeat="pirate in [{name: 'Jack Sparrow', profile: {age: 99}}, {name: 'Someone Else', profile: {age: 98}}]" rt-scope="pirate.profile as profile; 20 as twenty">
{number}-{numberIndex}-{fifteen}-{ten}-{pirateIndex}-{pirate.name}-{profile.age}
</div>
</div>
</div>
</div>

View File

@ -0,0 +1 @@
<div><div><div><div>111-0-15-10-0-Jack Sparrow-99</div><div>111-0-15-10-1-Someone Else-98</div></div></div></div>

View File

@ -2,6 +2,9 @@
var test = require('tape');
var reactTemplates = require('../../src/reactTemplates');
var context = require('../../src/context');
var util = require('./util');
var readFileNormalized = util.readFileNormalized;
var compareAndWrite = util.compareAndWrite;
var fs = require('fs');
var _ = require('lodash');
var path = require('path');
@ -10,14 +13,6 @@ var cheerio = require('cheerio');
var RTCodeError = reactTemplates.RTCodeError;
var dataPath = path.resolve(__dirname, '..', 'data');
/**
* @param {string} filename
* @return {string}
*/
function readFileNormalized(filename) {
return fs.readFileSync(filename).toString().replace(/\r/g, '').trim();
}
var invalidFiles = [
{file: 'invalid-scope.rt', issue: new RTCodeError("invalid scope part 'a in a in a'", 0, 35, 1, 1)},
{file: 'invalid-html.rt', issue: new RTCodeError('Document should have a root element', -1, -1, -1, -1)},
@ -38,7 +33,7 @@ test('invalid tests', function (t) {
function check(testFile) {
var filename = path.join(dataPath, testFile.file);
var html = readFileNormalized(filename);
var html = util.readFileNormalized(filename);
var error = null;
try {
reactTemplates.convertTemplateToReact(html);
@ -71,7 +66,7 @@ test('invalid tests json', function (t) {
function check(testFile) {
context.clear();
var filename = path.join(dataPath, testFile.file);
var options = {format: 'json'};
var options = {format: 'json', force: true};
cli.handleSingleFile(options, filename);
t.deepEqual(normalizeError(context.getMessages()[0]), errorEqualMessage(testFile.issue, filename), 'Expect cli to produce valid output messages');
}
@ -160,22 +155,6 @@ test('conversion test - native', function (t) {
}
});
/**
* @param {*} t
* @param {string} actual
* @param {string} expected
* @param {string} filename
* @return {boolean} whether actual is equal to expected
*/
function compareAndWrite(t, actual, expected, filename) {
t.equal(actual, expected);
if (actual !== expected) {
fs.writeFileSync(filename + '.actual.js', actual);
return false;
}
return true;
}
test('convert div with all module types', function (t) {
var files = [
{source: 'div.rt', expected: 'div.rt.commonjs.js', options: {modules: 'commonjs'}},
@ -212,20 +191,11 @@ test('convert jsrt and test source results', function (t) {
}
});
/**
* @param {string} html
* @return {string}
*/
function normalizeHtml(html) {
return cheerio.load(html, {normalizeWhitespace: true}).html()
.replace(/\>\s+/mg, '>')
.replace(/\s+\</mg, '<')
.replace(/\>\s+\</mg, '><');
}
test('html tests', function (t) {
var files = ['scope.rt', 'scope-trailing-semicolon.rt', 'scope-variable-references.rt', 'lambda.rt', 'eval.rt', 'props.rt', 'custom-element.rt', 'style.rt', 'concat.rt',
'js-in-attr.rt', 'props-class.rt', 'rt-class.rt', 'className.rt'];
'js-in-attr.rt', 'props-class.rt', 'rt-class.rt', 'className.rt',
'scope-evaluated-after-repeat.rt', 'scope-evaluated-after-repeat2.rt', 'scope-evaluated-after-if.rt'
];
t.plan(files.length);
files.forEach(check);
@ -251,8 +221,8 @@ test('html tests', function (t) {
render: eval(code) //eslint-disable-line no-eval
}));
var actual = React.renderToStaticMarkup(comp());
actual = normalizeHtml(actual);
expected = normalizeHtml(expected);
actual = util.normalizeHtml(actual);
expected = util.normalizeHtml(expected);
var equal = compareAndWrite(t, actual, expected, filename);
if (!equal) {
fs.writeFileSync(filename + '.code.js', code);