move reactTemplates internals to utils and refactor tests

This commit is contained in:
Omer Ganim 2016-05-02 13:21:21 +03:00
parent bcf9fa866b
commit 232e0de5cf
34 changed files with 430 additions and 382 deletions

View File

@ -9,14 +9,14 @@
"scripts": {
"build": "npm run lint && npm run test",
"lint": "eslint .",
"test": "node dist-test/src/test.js && node dist-test/src/styleTest.js",
"testd": "node test/src/test.js && node test/src/styleTest.js",
"test": "node dist-test/src/test.js",
"testd": "node test/src/test.js",
"test-cov": "istanbul cover test/src/test.js -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",
"patch": "npm version patch -m\"update version to %s\" && git push && git push --tags",
"minor": "npm version minor -m\"update version to %s\" && git push && git push --tags",
"major": "npm version major -m\"update version to %s\" && git push && git push --tags",
"buildwp": "webpack --config webpack-production.config.js --progress --profile --colors",
"babel": "rm -rf build && babel src/ --out-dir dist && babel test/src/ --out-dir dist-test/src && cp -R test/data dist-test/data",
"babel": "rm -rf build && babel src/ --out-dir dist && babel test/src/ --out-dir dist-test/src && cp -R test/data dist-test/",
"babel2": "rm -rf build && babel src/ --out-dir build/src && babel test/src/ --out-dir build/test/src && cp -R test/data build/test/data && cp package.json build/",
"all": "npm run lint && npm run babel && npm run test"
},

View File

@ -96,59 +96,6 @@ function reactImport(options) {
return 'react/addons';
}
/**
* @const
*/
const curlyMap = {'{': 1, '}': -1};
/**
* @typedef {{boundParams: Array.<string>, injectedFunctions: Array.<string>, html: string, options: *}} Context
*/
/**
* @typedef {{fileName:string,force:boolean,modules:string,defines:*,reactImportPath:string=,lodashImportPath:string=,flow:boolean,name:string,native:boolean,propTemplates:*,format:string,_:*,version:boolean,help:boolean,listTargetVersion:boolean,modules:string, dryRun:boolean}} Options
*/
/**
* @param node
* @param {Context} context
* @param {string} txt
* @return {string}
*/
function convertText(node, context, txt) {
let res = '';
let first = true;
const concatChar = node.type === 'text' ? ',' : '+';
while (_.includes(txt, '{')) {
const start = txt.indexOf('{');
const pre = txt.substr(0, start);
if (pre) {
res += (first ? '' : concatChar) + JSON.stringify(pre);
first = false;
}
let curlyCounter = 1;
let end;
for (end = start + 1; end < txt.length && curlyCounter > 0; end++) { //eslint-disable-line no-restricted-syntax
curlyCounter += curlyMap[txt.charAt(end)] || 0;
}
if (curlyCounter === 0) {
const needsParens = start !== 0 || end !== txt.length - 1;
res += (first ? '' : concatChar) + (needsParens ? '(' : '') + txt.substr(start + 1, end - start - 2) + (needsParens ? ')' : '');
first = false;
txt = txt.substr(end);
} else {
throw RTCodeError.build(context, node, `Failed to parse text '${txt}'`);
}
}
if (txt) {
res += (first ? '' : concatChar) + JSON.stringify(txt);
}
if (res === '') {
res = 'true';
}
return res;
}
/**
* @param {Context} context
* @param {string} namePrefix
@ -239,10 +186,10 @@ function generateProps(node, context) {
if (key === classSetAttr) {
props[propKey] = existing + classSetTemplate({classSet: val});
} else if (key === classAttr || key === reactSupport.classNameProp) {
props[propKey] = existing + convertText(node, context, val.trim());
props[propKey] = existing + utils.convertText(node, context, val.trim());
}
} else if (!_.startsWith(key, 'rt-')) {
props[propKey] = convertText(node, context, val.trim());
props[propKey] = utils.convertText(node, context, val.trim());
}
});
_.assign(props, generateTemplateProps(node, context));
@ -280,8 +227,7 @@ function handleStyleProp(val, node, context) {
const pair = i.split(':');
const value = pair.slice(1).join(':').trim();
return _.camelCase(pair[0].trim()) + ' : ' + convertText(node, context, value.trim());
//return stringUtils.convertToCamelCase(pair[0].trim()) + ' : ' + convertText(node, context, value.trim())
return _.camelCase(pair[0].trim()) + ' : ' + utils.convertText(node, context, value.trim());
})
.join(',');
return `{${styleStr}}`;
@ -434,7 +380,7 @@ function convertHtmlToReact(node, context) {
} else if (node.type === 'comment') {
return commentTemplate(node);
} else if (node.type === 'text') {
return node.data.trim() ? convertText(node, context, node.data) : '';
return node.data.trim() ? utils.convertText(node, context, node.data) : '';
}
}
@ -606,6 +552,5 @@ module.exports = {
convertRT,
convertJSRTToJS,
RTCodeError,
normalizeName: utils.normalizeName,
_test: {convertText}
normalizeName: utils.normalizeName
};

View File

@ -124,6 +124,61 @@ function usesScopeName(scopeNames, node) {
return false;
}
/**
* @const
*/
const curlyMap = {'{': 1, '}': -1};
/**
* @typedef {{boundParams: Array.<string>, injectedFunctions: Array.<string>, html: string, options: *}} Context
*/
/**
* @typedef {{fileName:string,force:boolean,modules:string,defines:*,reactImportPath:string=,lodashImportPath:string=,flow:boolean,name:string,native:boolean,propTemplates:*,format:string,_:*,version:boolean,help:boolean,listTargetVersion:boolean,modules:string, dryRun:boolean}} Options
*/
/**
* @param node
* @param {Context} context
* @param {string} txt
* @return {string}
*/
function convertText(node, context, txt) {
let res = '';
let first = true;
const concatChar = node.type === 'text' ? ',' : '+';
while (_.includes(txt, '{')) {
const start = txt.indexOf('{');
const pre = txt.substr(0, start);
if (pre) {
res += (first ? '' : concatChar) + JSON.stringify(pre);
first = false;
}
let curlyCounter = 1;
let end = start;
while (++end < txt.length && curlyCounter > 0) {
curlyCounter += curlyMap[txt.charAt(end)] || 0;
}
if (curlyCounter === 0) {
const needsParens = start !== 0 || end !== txt.length - 1;
res += (first ? '' : concatChar) + (needsParens ? '(' : '') + txt.substr(start + 1, end - start - 2) + (needsParens ? ')' : '');
first = false;
txt = txt.substr(end);
} else {
throw RTCodeError.build(context, node, `Failed to parse text '${txt}'`);
}
}
if (txt) {
res += (first ? '' : concatChar) + JSON.stringify(txt);
}
if (res === '') {
res = 'true';
}
return res;
}
module.exports = {
usesScopeName,
normalizeName,
@ -131,5 +186,6 @@ module.exports = {
isStringOnlyCode,
concatChildren,
validate,
addIfMissing
addIfMissing,
convertText
};

31
test/src/fsUtil.spec.js Normal file
View File

@ -0,0 +1,31 @@
'use strict';
const fs = require('fs');
const fsUtil = require('../../src/fsUtil');
const path = require('path');
module.exports = {
runTests(test, dataPath) {
test('test isStale', t => {
const a = path.join(dataPath, 'a.tmp');
const b = path.join(dataPath, 'b.tmp');
fs.writeFileSync(a, 'actual');
fs.writeFileSync(b, 'actual');
const mtime1 = new Date(1995, 11, 17, 3, 24, 0);
fs.utimesSync(a, mtime1, mtime1);
const mtime2 = new Date(1995, 11, 17, 3, 24, 1);
fs.utimesSync(b, mtime2, mtime2);
let actual = fsUtil.isStale(a, b);
t.equal(actual, false);
actual = fsUtil.isStale(b, a);
t.equal(actual, true);
fs.unlinkSync(a);
fs.unlinkSync(b);
t.end();
});
}
};

View File

@ -0,0 +1,98 @@
'use strict';
const reactTemplates = require('../../src/reactTemplates');
const testUtils = require('./testUtils');
const _ = require('lodash');
const path = require('path');
const RTCodeError = reactTemplates.RTCodeError;
const omitStack = err => _.omit(err, 'stack', 'toIssue');
module.exports = {
runTests(test, basePath) {
const dataPath = path.resolve(basePath, 'invalid');
const invalidFiles = [
{file: 'if-with-scope/invalid-if-scope-1.rt', issue: new RTCodeError("invalid scope mapping used in if part 'this.bar(activeUsers.length)'", 0, 160, 1, 1)},
{file: 'if-with-scope/invalid-if-scope-2.rt', issue: new RTCodeError("invalid scope mapping used in if part 'this.bar[activeUsers || 0]'", 0, 158, 1, 1)},
{file: 'if-with-scope/invalid-if-scope-3.rt', issue: new RTCodeError("invalid scope mapping used in if part 'this.foo + activeUsers.length > this.bar'", 0, 172, 1, 1)},
{file: 'if-with-scope/invalid-if-scope-4.rt', issue: new RTCodeError("invalid scope mapping used in if part 'getCurrentActiveUsers().length'", 0, 170, 1, 1)},
{file: 'if-with-scope/invalid-if-scope-5.rt', issue: new RTCodeError("invalid scope mapping used in if part 'this.bar({activeUsers})'", 0, 155, 1, 1)},
{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)},
{file: 'invalid-exp.rt', issue: new RTCodeError("Failed to parse text '\n {z\n'", 5, 13, 1, 6)},
{file: 'invalid-lambda.rt', issue: new RTCodeError("when using 'on' events, use lambda '(p1,p2)=>body' notation or use {} to return a callback function. error: [onClick='']", 0, 23, 1, 1)},
{file: 'invalid-js.rt', issue: new RTCodeError('Unexpected token ILLEGAL', 0, 32, 1, 1)},
{file: 'invalid-single-root.rt', issue: new RTCodeError('Document should have no more than a single root element', 12, 23, 2, 1)},
{file: 'invalid-repeat.rt', issue: new RTCodeError('rt-repeat invalid \'in\' expression \'a in b in c\'', 0, 35, 1, 1)},
{file: 'invalid-rt-require.rt', issue: new RTCodeError("rt-require needs 'dependency' and 'as' attributes", 0, 14, 1, 1)},
{file: 'invalid-brace.rt', issue: new RTCodeError('Unexpected end of input', 128, 163, 5, 11)},
{file: 'invalid-style.rt', issue: new RTCodeError('Unexpected token ILLEGAL', 10, 39, 2, 5)},
{file: 'invalid-virtual.rt', issue: new RTCodeError('Document should not have <rt-virtual> as root element', 0, 60, 1, 1)}
];
test('invalid tests', t => {
t.plan(invalidFiles.length);
invalidFiles.forEach(check);
function check(testFile) {
const filename = path.join(dataPath, testFile.file);
const html = testUtils.readFileNormalized(filename);
let error = null;
try {
reactTemplates.convertTemplateToReact(html);
} catch (e) {
error = e;
}
t.deepEqual(omitStack(error), omitStack(testFile.issue), 'Expect convertTemplateToReact to throw an error');
}
});
/**
* @param {ERR} err
* @return {ERR}
*/
function normalizeError(err) {
err.msg = err.msg.replace(/\r/g, '');
return err;
}
test('invalid tests json', t => {
const cli = require('../../src/cli');
const context = require('../../src/context');
t.plan(invalidFiles.length);
invalidFiles.forEach(check);
function check(testFile) {
context.clear();
const filename = path.join(dataPath, testFile.file);
const 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');
}
});
/**
* @typedef {{index: number, line: number, column: number, msg: string, level: string, file: string}} ERR
*/
/**
* @param {RTCodeError} err
* @param {string} file
* @return {ERR}
*/
function errorEqualMessage(err, file) {
return {
index: err.index,
line: err.line,
column: err.column,
startOffset: err.startOffset,
endOffset: err.endOffset,
msg: err.message,
level: 'ERROR',
file
};
}
}
};

145
test/src/rt.valid.spec.js Normal file
View File

@ -0,0 +1,145 @@
'use strict';
const reactTemplates = require('../../src/reactTemplates');
const testUtils = require('./testUtils');
const readFileNormalized = testUtils.readFileNormalized;
const compareAndWrite = testUtils.compareAndWrite;
const path = require('path');
const context = require('../../src/context');
const fsUtil = require('../../src/fsUtil');
const fs = require('fs');
module.exports = {
runTests(test, dataPath) {
function checkFile(t, options, testFile) {
const filename = path.join(dataPath, testFile);
const html = readFileNormalized(filename);
const expected = readFileNormalized(filename + '.js');
const actual = reactTemplates.convertTemplateToReact(html, options).replace(/\r/g, '').trim();
compareAndWrite(t, actual, expected, filename);
}
function testFiles(t, files, options) {
t.plan(files.length);
files.forEach(checkFile.bind(this, t, options));
}
test('rt-if with rt-scope test', t => {
const files = ['if-with-scope/valid-if-scope.rt'];
testFiles(t, files);
});
test('conversion test', t => {
const files = ['div.rt', 'test.rt', 'repeat.rt', 'inputs.rt', 'require.rt'];
testFiles(t, files);
});
test('prop template conversion test', t => {
const options = {
propTemplates: {
List: {
Row: {prop: 'renderRow', arguments: ['rowData']}
}
}
};
const files = ['propTemplates/simpleTemplate.rt', 'propTemplates/templateInScope.rt', 'propTemplates/implicitTemplate.rt', 'propTemplates/twoTemplates.rt'];
testFiles(t, files, options);
});
test('conversion test - native', t => {
const options = {
propTemplates: {
MyComp: {
Row: {prop: 'renderRow', arguments: ['rowData']}
}
},
native: true
};
const files = ['native/nativeView.rt', 'native/listViewTemplate.rt', 'native/listViewAndCustomTemplate.rt'];
testFiles(t, files, options);
});
test('convert div with all module types', t => {
const files = [
{source: 'div.rt', expected: 'div.rt.commonjs.js', options: {modules: 'commonjs'}},
{source: 'div.rt', expected: 'div.rt.amd.js', options: {modules: 'amd', name: 'div'}},
{source: 'div.rt', expected: 'div.rt.globals.js', options: {modules: 'none', name: 'div'}},
{source: 'div.rt', expected: 'div.rt.es6.js', options: {modules: 'es6', name: 'div'}},
{source: 'div.rt', expected: 'div.rt.typescript.ts', options: {modules: 'typescript'}}
];
t.plan(files.length);
files.forEach(check);
function check(testData) {
const filename = path.join(dataPath, testData.source);
const html = readFileNormalized(filename);
const expected = readFileNormalized(path.join(dataPath, testData.expected));
const actual = reactTemplates.convertTemplateToReact(html, testData.options).replace(/\r/g, '').trim();
compareAndWrite(t, actual, expected, filename);
}
});
test('convert jsrt and test source results', t => {
const files = ['simple.jsrt'];
t.plan(files.length);
files.forEach(check);
function check(file) {
const filename = path.join(dataPath, file);
const js = readFileNormalized(filename);
const expected = readFileNormalized(path.join(dataPath, file.replace('.jsrt', '.js')));
const actual = reactTemplates.convertJSRTToJS(js, context).replace(/\r/g, '').trim();
compareAndWrite(t, actual, expected, filename);
}
});
test('html tests', t => {
const 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',
'svg.rt',
'virtual.rt',
'scope-evaluated-after-repeat.rt',
'scope-evaluated-after-repeat2.rt',
'scope-evaluated-after-if.rt',
'scope-obj.rt',
'repeat-literal-collection.rt',
'include.rt'
];
t.plan(files.length);
files.forEach(check);
function check(testFile) {
const filename = path.join(dataPath, testFile);
const options = {
readFileSync: fsUtil.createRelativeReadFileSync(filename)
};
let code = '';
try {
const html = fs.readFileSync(filename).toString();
const expected = testUtils.normalizeHtml(readFileNormalized(filename + '.html'));
code = reactTemplates.convertTemplateToReact(html, options).replace(/\r/g, '');
const actual = testUtils.normalizeHtml(testUtils.codeToHtml(code));
const equal = compareAndWrite(t, actual, expected, filename);
if (!equal) {
fs.writeFileSync(filename + '.code.js', code);
}
} catch (e) {
console.log(testFile, e);
fs.writeFileSync(filename + '.code.js', code);
}
}
});
}
};

14
test/src/rtStyle.spec.js Normal file
View File

@ -0,0 +1,14 @@
'use strict';
const rtStyle = require('../../src/rtStyle');
module.exports = {
runTests(test) {
test('html tests', t => {
const text = '.text { background-color: #00346E; padding: 3px; }';
const expected = '{\n "text": {\n "backgroundColor": "#00346E",\n "padding": 3\n }\n}';
const actual = rtStyle.convertBody(text);
t.equal(actual, expected);
t.end();
});
}
};

53
test/src/shell.spec.js Normal file
View File

@ -0,0 +1,53 @@
'use strict';
const context = require('../../src/context');
const _ = require('lodash');
const path = require('path');
module.exports = {
runTests(test, dataPath) {
test('test context', t => {
context.clear();
t.equal(context.hasErrors(), false);
context.error('hi', '', 1, 1);
t.equal(context.hasErrors(), true);
context.clear();
t.equal(context.hasErrors(), false);
t.end();
});
test('test shell', t => {
const shell = require('../../src/shell');
const newContext = _.cloneDeep(context);
let outputJSON = '';
newContext.options.format = 'json';
newContext.report = function (text) { outputJSON = text; };
let r = shell.printResults(newContext);
t.equal(r, 0);
context.error('hi', '', 1, 1);
r = shell.printResults(newContext);
t.equal(r, 1);
const output = JSON.parse(outputJSON);
t.deepEqual(output, [{
column: 1,
endOffset: -1,
file: null,
index: -1,
level: 'ERROR',
line: 1,
msg: 'hi',
startOffset: -1
}]);
context.clear();
t.end();
});
test('test shell', t => {
const filename = path.join(dataPath, 'div.rt');
const cli = require('../../src/cli');
const r = cli.execute(`${filename} -r --dry-run`);
t.equal(r, 0);
t.end();
});
}
};

View File

@ -1,11 +0,0 @@
'use strict';
const test = require('tape');
const rtStyle = require('../../src/rtStyle');
const text = '.text { background-color: #00346E; padding: 3px; }';
const textEp = '{\n "text": {\n "backgroundColor": "#00346E",\n "padding": 3\n }\n}';
test('html tests', t => {
const res = rtStyle.convertBody(text);
t.equal(res, textEp);
t.end();
});

View File

@ -1,313 +1,10 @@
'use strict';
const test = require('tape');
const reactTemplates = require('../../src/reactTemplates');
const context = require('../../src/context');
const util = require('./util');
const fsUtil = require('../../src/fsUtil');
const readFileNormalized = util.readFileNormalized;
const compareAndWrite = util.compareAndWrite;
const fs = require('fs');
const _ = require('lodash');
const path = require('path');
const RTCodeError = reactTemplates.RTCodeError;
const dataPath = path.resolve(__dirname, '..', 'data');
const invalidFiles = [
{file: 'if-with-scope/invalid-if-scope-1.rt', issue: new RTCodeError("invalid scope mapping used in if part 'this.bar(activeUsers.length)'", 0, 160, 1, 1)},
{file: 'if-with-scope/invalid-if-scope-2.rt', issue: new RTCodeError("invalid scope mapping used in if part 'this.bar[activeUsers || 0]'", 0, 158, 1, 1)},
{file: 'if-with-scope/invalid-if-scope-3.rt', issue: new RTCodeError("invalid scope mapping used in if part 'this.foo + activeUsers.length > this.bar'", 0, 172, 1, 1)},
{file: 'if-with-scope/invalid-if-scope-4.rt', issue: new RTCodeError("invalid scope mapping used in if part 'getCurrentActiveUsers().length'", 0, 170, 1, 1)},
{file: 'if-with-scope/invalid-if-scope-5.rt', issue: new RTCodeError("invalid scope mapping used in if part 'this.bar({activeUsers})'", 0, 155, 1, 1)},
{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)},
{file: 'invalid-exp.rt', issue: new RTCodeError("Failed to parse text '\n {z\n'", 5, 13, 1, 6)},
{file: 'invalid-lambda.rt', issue: new RTCodeError("when using 'on' events, use lambda '(p1,p2)=>body' notation or use {} to return a callback function. error: [onClick='']", 0, 23, 1, 1)},
{file: 'invalid-js.rt', issue: new RTCodeError('Unexpected token ILLEGAL', 0, 32, 1, 1)},
{file: 'invalid-single-root.rt', issue: new RTCodeError('Document should have no more than a single root element', 12, 23, 2, 1)},
{file: 'invalid-repeat.rt', issue: new RTCodeError('rt-repeat invalid \'in\' expression \'a in b in c\'', 0, 35, 1, 1)},
{file: 'invalid-rt-require.rt', issue: new RTCodeError("rt-require needs 'dependency' and 'as' attributes", 0, 14, 1, 1)},
{file: 'invalid-brace.rt', issue: new RTCodeError('Unexpected end of input', 128, 163, 5, 11)},
{file: 'invalid-style.rt', issue: new RTCodeError('Unexpected token ILLEGAL', 10, 39, 2, 5)},
{file: 'invalid-virtual.rt', issue: new RTCodeError('Document should not have <rt-virtual> as root element', 0, 60, 1, 1)}
];
const specs = ['rt.invalid', 'rt.valid', 'utils', 'shell', 'rtStyle', 'fsUtil'];
test('invalid tests', t => {
t.plan(invalidFiles.length);
invalidFiles.forEach(check);
function check(testFile) {
const filename = path.join(dataPath, testFile.file);
const html = util.readFileNormalized(filename);
let error = null;
try {
reactTemplates.convertTemplateToReact(html);
} catch (e) {
error = e;
}
t.deepEqual(omitStack(error), omitStack(testFile.issue), 'Expect convertTemplateToReact to throw an error');
}
});
function omitStack(err) {
return _.omit(err, 'stack', 'toIssue');
}
/**
* @param {ERR} err
* @return {ERR}
*/
function normalizeError(err) {
err.msg = err.msg.replace(/\r/g, '');
return err;
}
test('invalid tests json', t => {
const cli = require('../../src/cli');
t.plan(invalidFiles.length);
invalidFiles.forEach(check);
function check(testFile) {
context.clear();
const filename = path.join(dataPath, testFile.file);
const 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');
}
});
/**
* @typedef {{index: number, line: number, column: number, msg: string, level: string, file: string}} ERR
*/
/**
* @param {RTCodeError} err
* @param {string} file
* @return {ERR}
*/
function errorEqualMessage(err, file) {
return {
index: err.index,
line: err.line,
column: err.column,
startOffset: err.startOffset,
endOffset: err.endOffset,
msg: err.message,
level: 'ERROR',
file
};
}
test('rt-if with rt-scope test', t => {
const files = ['if-with-scope/valid-if-scope.rt'];
testFiles(t, files);
});
test('conversion test', t => {
const files = ['div.rt', 'test.rt', 'repeat.rt', 'inputs.rt', 'require.rt'];
testFiles(t, files);
});
test('prop template conversion test', t => {
const options = {
propTemplates: {
List: {
Row: {prop: 'renderRow', arguments: ['rowData']}
}
}
};
const files = ['propTemplates/simpleTemplate.rt', 'propTemplates/templateInScope.rt', 'propTemplates/implicitTemplate.rt', 'propTemplates/twoTemplates.rt'];
testFiles(t, files, options);
});
function checkFile(t, options, testFile) {
const filename = path.join(dataPath, testFile);
const html = readFileNormalized(filename);
const expected = readFileNormalized(filename + '.js');
const actual = reactTemplates.convertTemplateToReact(html, options).replace(/\r/g, '').trim();
compareAndWrite(t, actual, expected, filename);
}
function testFiles(t, files, options) {
t.plan(files.length);
files.forEach(checkFile.bind(this, t, options));
}
test('conversion test - native', t => {
const options = {
propTemplates: {
MyComp: {
Row: {prop: 'renderRow', arguments: ['rowData']}
}
},
native: true
};
const files = ['nativeView.rt', 'listViewTemplate.rt', 'listViewAndCustomTemplate.rt'];
testFiles(t, files, options);
});
test('convert div with all module types', t => {
const files = [
{source: 'div.rt', expected: 'div.rt.commonjs.js', options: {modules: 'commonjs'}},
{source: 'div.rt', expected: 'div.rt.amd.js', options: {modules: 'amd', name: 'div'}},
{source: 'div.rt', expected: 'div.rt.globals.js', options: {modules: 'none', name: 'div'}},
{source: 'div.rt', expected: 'div.rt.es6.js', options: {modules: 'es6', name: 'div'}},
{source: 'div.rt', expected: 'div.rt.typescript.ts', options: {modules: 'typescript'}}
];
t.plan(files.length);
files.forEach(check);
function check(testData) {
const filename = path.join(dataPath, testData.source);
const html = readFileNormalized(filename);
const expected = readFileNormalized(path.join(dataPath, testData.expected));
const actual = reactTemplates.convertTemplateToReact(html, testData.options).replace(/\r/g, '').trim();
compareAndWrite(t, actual, expected, filename);
}
});
test('convert jsrt and test source results', t => {
const files = ['simple.jsrt'];
t.plan(files.length);
files.forEach(check);
function check(file) {
const filename = path.join(dataPath, file);
const js = readFileNormalized(filename);
const expected = readFileNormalized(path.join(dataPath, file.replace('.jsrt', '.js')));
const actual = reactTemplates.convertJSRTToJS(js, context).replace(/\r/g, '').trim();
compareAndWrite(t, actual, expected, filename);
}
});
test('html tests', t => {
const 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',
'svg.rt',
'virtual.rt',
'scope-evaluated-after-repeat.rt',
'scope-evaluated-after-repeat2.rt',
'scope-evaluated-after-if.rt',
'scope-obj.rt',
'repeat-literal-collection.rt',
'include.rt'
];
t.plan(files.length);
files.forEach(check);
function check(testFile) {
const filename = path.join(dataPath, testFile);
const options = {
readFileSync: fsUtil.createRelativeReadFileSync(filename)
};
let code = '';
try {
const html = fs.readFileSync(filename).toString();
const expected = util.normalizeHtml(readFileNormalized(filename + '.html'));
code = reactTemplates.convertTemplateToReact(html, options).replace(/\r/g, '');
const actual = util.normalizeHtml(util.codeToHtml(code));
const equal = compareAndWrite(t, actual, expected, filename);
if (!equal) {
fs.writeFileSync(filename + '.code.js', code);
}
} catch (e) {
console.log(testFile, e);
fs.writeFileSync(filename + '.code.js', code);
}
}
});
test('test context', t => {
context.clear();
t.equal(context.hasErrors(), false);
context.error('hi', '', 1, 1);
t.equal(context.hasErrors(), true);
context.clear();
t.equal(context.hasErrors(), false);
t.end();
});
test('test shell', t => {
const shell = require('../../src/shell');
const newContext = _.cloneDeep(context);
let outputJSON = '';
newContext.options.format = 'json';
newContext.report = function (text) { outputJSON = text; };
let r = shell.printResults(newContext);
t.equal(r, 0);
context.error('hi', '', 1, 1);
r = shell.printResults(newContext);
t.equal(r, 1);
const output = JSON.parse(outputJSON);
t.deepEqual(output, [{
column: 1,
endOffset: -1,
file: null,
index: -1,
level: 'ERROR',
line: 1,
msg: 'hi',
startOffset: -1
}]);
context.clear();
t.end();
});
test('test shell', t => {
const filename = path.join(dataPath, 'div.rt');
const cli = require('../../src/cli');
const r = cli.execute(`${filename} -r --dry-run`);
t.equal(r, 0);
t.end();
});
test('test convertText', t => {
const texts = [
{input: '{}', expected: '()'},
{input: "a {'b'}", expected: '"a "+(\'b\')'}
];
t.plan(texts.length);
texts.forEach(check);
function check(testData) {
const r = reactTemplates._test.convertText({}, {}, testData.input);
t.equal(r, testData.expected);
}
});
test('util.isStale', t => {
const a = path.join(dataPath, 'a.tmp');
const b = path.join(dataPath, 'b.tmp');
fs.writeFileSync(a, 'actual');
fs.writeFileSync(b, 'actual');
const mtime1 = new Date(1995, 11, 17, 3, 24, 0);
fs.utimesSync(a, mtime1, mtime1);
const mtime2 = new Date(1995, 11, 17, 3, 24, 1);
fs.utimesSync(b, mtime2, mtime2);
let actual = fsUtil.isStale(a, b);
t.equal(actual, false);
actual = fsUtil.isStale(b, a);
t.equal(actual, true);
fs.unlinkSync(a);
fs.unlinkSync(b);
t.end();
});
specs
.map(file => require(`./${file}.spec`))
.forEach(spec => spec.runTests(test, dataPath));

20
test/src/utils.spec.js Normal file
View File

@ -0,0 +1,20 @@
'use strict';
const utils = require('../../src/utils');
module.exports = {
runTests(test) {
test('test convertText', t => {
const texts = [
{input: '{}', expected: '()'},
{input: "a {'b'}", expected: '"a "+(\'b\')'}
];
t.plan(texts.length);
texts.forEach(check);
function check(testData) {
const r = utils.convertText({}, {}, testData.input);
t.equal(r, testData.expected);
}
});
}
};