babel-plugin-gwt
Version:
Data Driven Testing babel plugin inspired by Groovy's Spock framework
183 lines (159 loc) • 5.64 kB
JavaScript
module.exports = gwtPlugin;
const getLeftFirstData = node => {
let acc = [];
if (node.left) {
acc.push(...getLeftFirstData(node.left));
}
if (node.right) {
acc.push(...getLeftFirstData(node.right));
}
if (!node.left && !node.right) {
acc.push(node);
}
return acc;
};
const hasBodyFunction = index => args =>
looksLike(args[index], {
type: t => t === 'ArrowFunctionExpression' || t === 'FunctionExpression'
});
function gwtPlugin({ types: t, transform }) {
return {
name: 'gwt',
visitor: {
ExpressionStatement(path) {
const isTestBlock = looksLike(path, {
node: {
expression: {
callee: {
type: 'Identifier',
name: n => n === 'it' || n === 'test' || n === 'fit' || n === 'ftest' || n === 'xit' || n === 'xtest'
}
}
}
});
const isOnlyBlock =
looksLike(path, {
node: {
expression: {
callee: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: n => n === 'it' || n === 'test'
},
property: {
type: 'Identifier',
name: 'only'
}
},
arguments: hasBodyFunction(1)
}
}
});
const isSkipBlock =
looksLike(path, {
node: {
expression: {
callee: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: n => n === 'it' || n === 'test'
},
property: {
type: 'Identifier',
name: 'skip'
}
},
arguments: hasBodyFunction(1)
}
}
});
console.log(transform('const a = 1'))
if (!isTestBlock && !isOnlyBlock && !isSkipBlock) return;
const testType = path.node.expression.callee;
const getLabelTitle = (labels, name) => {
const label = labels.find(stmt => looksLike(stmt.label, { type: 'Identifier', name }));
// todo add maybe.fromNullable
return label ? label.body.expression.value : '';
};
const getWhereRows = labels => {
const label = labels.find(stmt => looksLike(stmt.label, { type: 'Identifier', name: 'where' }));
// todo add maybe.fromNullable
if (label) {
return label.body.body.map(node => getLeftFirstData(node.expression));
}
return [];
};
const labels = path.node.expression.arguments[1].body.body.filter(stmt => t.isLabeledStatement(stmt));
const given = getLabelTitle(labels, 'given');
const when = getLabelTitle(labels, 'when');
const then = getLabelTitle(labels, 'then');
const whereRows = getWhereRows(labels);
const whereTitles = whereRows.length > 0 ? whereRows[0].map(({ name }) => name) : [];
if (!given && !when && !then && !whereTitles.length && !whereRows.length) return;
let testTitle = path.node.expression.arguments[0].value;
if (given) {
testTitle = `${testTitle} given: ${given}`;
}
if (when) {
testTitle = `${testTitle} when: ${when}`;
}
if (then) {
testTitle = `${testTitle} then: ${then}`;
}
// t.expressionStatement(expression)
// t.callExpression(callee, arguments)
// t.memberExpression(object, property, computed, optional)
// t.arrowFunctionExpression(params, body, async)
// t.templateLiteral(quasis, expressions)
const quasi = testTitle
.split(/#\w+/g)
.map((a, index) => t.templateElement({ raw: a }, index == testTitle.split(/\$\{\w+\}/g).length - 1));
const expressions = (testTitle.match(/#\w+/g) || []).map(s => t.identifier(s.substring(1)));
const testTitleAst = t.templateLiteral(quasi, expressions);
const testRows = whereRows.slice(1).map(row =>
t.objectExpression(
whereTitles.map((title, index) => {
return t.objectProperty(t.identifier(title), row[index]);
})
)
);
const testDataTitles = whereTitles.map(title =>
t.objectProperty(t.identifier(title), t.identifier(title), false, true)
);
const body = path.node.expression.arguments[1].body.body.filter(stmt => !t.isLabeledStatement(stmt));
const y = t.expressionStatement(
t.callExpression(t.memberExpression(t.arrayExpression(testRows), t.identifier('forEach')), [
t.arrowFunctionExpression(
[t.objectPattern(testDataTitles)],
t.blockStatement([
t.expressionStatement(
t.callExpression(testType, [testTitleAst, t.arrowFunctionExpression([], t.blockStatement(body))])
)
])
)
])
);
path.replaceWith(y);
}
}
};
}
function looksLike(a, b) {
return (
a &&
b &&
Object.keys(b).every(bKey => {
const bVal = b[bKey];
const aVal = a[bKey];
if (typeof bVal === 'function') {
return bVal(aVal);
}
return isPrimitive(bVal) ? bVal === aVal : looksLike(aVal, bVal);
})
);
}
function isPrimitive(val) {
return val == null || /^[sbn]/.test(typeof val);
}