Skip to content

Instantly share code, notes, and snippets.

@traviskaufman
Created February 16, 2017 21:09
Show Gist options
  • Save traviskaufman/59476a12b65925d06b048b61b8ef085c to your computer and use it in GitHub Desktop.
Save traviskaufman/59476a12b65925d06b048b61b8ef085c to your computer and use it in GitHub Desktop.
one-off script to transform MDC-Web's tests from tape to mocha (https://github.com/material-components/material-components-web/issues/143)
/**
* @fileoverview one-off script to transform MDC-Web's tests from tape to mocha.
* See https://github.com/material-components/material-components-web/issues/143
*/
const fs = require('fs');
const path = require('path');
const babylon = require('babylon');
const glob = require('glob');
const recast = require('recast');
const t = require('babel-types');
const traverse = require('babel-traverse').default;
const TEST_DIR = path.resolve(__dirname, '../test/unit');
main();
function main() {
log('Collecting test files');
const testFilePaths = glob.sync(`${TEST_DIR}/**/*.js`);
for (const filePath of testFilePaths) {
if (/fake\-tape\.js$/.test(filePath)) {
continue;
}
log('Transforming', filePath);
const transformed = transform(filePath);
fs.writeFileSync(filePath, transformed.code, 'utf8');
}
}
function transform(filePath) {
const source = fs.readFileSync(filePath, 'utf8');
const ast = recast.parse(source, {
parser: {
parse: (code) => babylon.parse(code, {sourceType: 'module'}),
},
});
traverse(ast, {
// Transform `import test from 'tape' -> import {assert} from 'chai'`
'ImportDeclaration'({node}) {
const [specifier] = node.specifiers;
const isImportTestFromTape = (
t.isImportDefaultSpecifier(specifier) &&
t.isIdentifier(specifier.local, {name: 'test'}) &&
t.isLiteral(node.source, {value: 'tape'})
);
if (isImportTestFromTape) {
const importAssert = t.importSpecifier(t.identifier('assert'), t.identifier('assert'));
const fromChai = t.stringLiteral('chai');
node.specifiers = [importAssert];
node.source = fromChai;
}
},
// Handle verifyDefaultAdapter declaration by shifting the last test arg off of it
'ExportNamedDeclaration'({node}) {
const isExportVerifyDefaultAdapter = (
t.isFunctionDeclaration(node.declaration) &&
t.isIdentifier(node.declaration.id, {name: 'verifyDefaultAdapter'})
);
if (!isExportVerifyDefaultAdapter) {
return;
}
const {params} = node.declaration;
t.assertIdentifier(params[params.length - 1], {name: 't'});
params.pop();
return;
},
// Handle the numerous fn call rewrites needed
'CallExpression'(path) {
const {node} = path;
// Remove the `t` argument within each `test()` fn
const isTapeTest = t.isIdentifier(node.callee, {name: 'test'});
if (isTapeTest) {
// remove the `t` argument from the arrow fn
const testFn = node.arguments[node.arguments.length - 1];
t.assertArrowFunctionExpression(testFn);
t.assertIdentifier(testFn.params[0], {name: 't'});
testFn.params.pop();
return;
}
// Fix verifyDefaultAdapter calls within tests by shifting the last argument out.
const isVerifyDefaultAdapter = t.isIdentifier(node.callee, {name: 'verifyDefaultAdapter'});
if (isVerifyDefaultAdapter) {
t.assertIdentifier(node.arguments[node.arguments.length - 1], {name: 't'});
node.arguments.pop();
return;
}
// Fix testFoundation() methods
// testFoundation('...', (t) => {
// const {...} = t.data;
// ...
// });
// -->
// testFoundation('...', ({...}) => {
// ...
// });
// NOTE: testFoundation() methods need to be manually changed
const isTestFoundation = t.isIdentifier(node.callee, {name: 'testFoundation'});
if (isTestFoundation) {
traverse(node, {
'VariableDeclarator'(childPath) {
const {node: childNode} = childPath;
const isDataVar = (
t.isMemberExpression(childNode.init) &&
t.isIdentifier(childNode.init.object, {name: 't'}) &&
t.isIdentifier(childNode.init.property, {name: 'data'})
);
if (!isDataVar) {
return;
}
const fnArg = node.arguments[node.arguments.length - 1];
t.assertArrowFunctionExpression(fnArg);
fnArg.params = [childNode.id];
childPath.remove();
},
}, path.scope, path.state, path.parentPath);
return;
}
// Remap the t.* methods into assert.*, unwrapping any td.verify() statements we find
// along the way.
const isTapeMethod = (
t.isMemberExpression(node.callee) &&
t.isIdentifier(node.callee.object, {name: 't'}) &&
t.isIdentifier(node.callee.property)
);
if (!isTapeMethod) {
return;
}
const {name: methodName} = node.callee.property;
switch (methodName) {
// -> assert.assert(false, ...args)
case 'fail':
node.callee = t.memberExpression(t.identifier('assert'), t.identifier('fail'));
node.arguments.shift(t.booleanLiteral(false));
break;
// -> t.<name>(...args) ==> assert.<name>(...args)
case 'true':
node.callee = t.memberExpression(t.identifier('assert'), t.identifier('isTrue'));
break;
case 'false':
node.callee = t.memberExpression(t.identifier('assert'), t.identifier('isFalse'));
break;
case 'ok':
case 'notOk':
case 'error':
case 'equal':
case 'notEqual':
case 'deepEqual':
case 'notDeepEqual':
case 'throws':
case 'equals':
node.callee = t.memberExpression(t.identifier('assert'), t.identifier(methodName));
break;
case 'doesNotThrow':
node.callee = t.memberExpression(t.identifier('assert'), t.identifier('doesNotThrow'));
// Special case to unwrap td.verify()
// -> t.doesNotThrow(() => td.verify(...args)) => td.verify(...args)
const [arg] = node.arguments;
if (!t.isArrowFunctionExpression(arg)) {
return;
}
let body;
// t.doesNotThrow(() => td.verify(...args));
if (t.isCallExpression(arg.body)) {
body = arg.body;
} else if (
// t.doesNotThrow(() => {
// td.verify(...args);
// });
t.isBlockStatement(arg.body) &&
arg.body.body.length === 1 &&
t.isExpressionStatement(arg.body.body[0])
) {
body = arg.body.body[0].expression;
} else {
return;
}
const isTdVerify = (
t.isIdentifier(body.callee.object, {name: 'td'}) &&
t.isIdentifier(body.callee.property, {name: 'verify'})
);
if (isTdVerify) {
path.replaceWith(body);
}
break;
default:
path.remove();
}
},
});
return recast.print(ast, {
objectCurlySpacing: false,
quote: 'single',
trailingComma: {
objects: true,
arrays: true,
parameters: false,
},
});
}
function log(...args) {
console.log('[tape2mocha]', ...args);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment