Created
February 16, 2017 21:09
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @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