walkitout
Version:
Run a callback, asynchronously, on all filepaths in a directory tree
238 lines (199 loc) • 6.33 kB
JavaScript
var assert = require('assert');
var fs = require('fs');
var path = require('path');
var vm = require('vm');
var mocks = require('./lib/mocks');
var startPath = '.';
main('index.js');
function testErrors(code, codeFile, tree)
{
vm.runInThisContext(code, codeFile);
var actualPaths = [];
var expectedPaths = mocks.getExpectedPaths(tree);
var finishHandled = false;
var pathCheckWait = 1;
// Wait n seconds and then check
// actualPaths against expectedPaths
var pathCheckTimer = setTimeout(function ()
{
var i = 0;
var containsPath;
var assertPaths = "\n\nactual:\n" + actualPaths.join("\n")
+ "\n\nexpected:\n" + expectedPaths.join("\n");
assert.strictEqual(actualPaths.length, expectedPaths.length,
'Actual paths should have a count of ' + expectedPaths.length
+ ' not ' + actualPaths.length + assertPaths);
console.log('actual paths count is OK');
while (i < expectedPaths.length)
{
containsPath = actualPaths.indexOf(expectedPaths[i]) >= 0 ? true : false;
assert.equal(containsPath, true,
'Actual paths should contain "' + expectedPaths[i] + '"'
+ ' but it does not.' + assertPaths);
i += 1;
}
console.log('actual paths match expected OK');
assert.equal(finishHandled, true,
'walkitout should call the finish handler');
console.log('finish handler invoked OK');
checkDepth(expectedPaths);
}, pathCheckWait * 1000);
walkitout(startPath,
function callbackHandler(err, filename, done) {
// skip on error.
// see filterForExpectedPaths()
if (err) return done();
actualPaths.push(filename);
done();
},
function finishHandler() {
// This will be checked
finishHandled = true;
}
);
function checkDepth(expectedFilenames) {
walkitout(startPath,
function callbackHandler(err, filename, done) {
done();
},
function finishHandler() {
console.log('descent depths OK');
checkScopes(expectedFilenames);
},
null,
function (dirname, dirpath, descend, skip, depth) {
var normalizedDepth = dirpath.split(path.sep).length;
if (dirpath.indexOf(startPath) !== 0) {
normalizedDepth += 1;
}
assert.strictEqual(depth, normalizedDepth,
"dirpath '" + dirpath + path.sep + "' depth should be " +
normalizedDepth + " not " + depth);
descend();
}
);
}
}
function main(codeFile)
{
fs.readFile(
codeFile, { encoding: 'utf8' },
function (err, code) {
var tree = mocks.enable();
testErrors(code, codeFile, tree);
}
);
}
function checkWalkControl(expectedFilenames)
{
var actualFilenames = [];
function okFilename(filename) {
return !/^\.|coverage|node_modules|test/.test(filename);
}
walkitout(startPath,
// handler
function (err, filename, done) {
if (err) return done();
if (okFilename(filename)) {
actualFilenames.push(filename);
}
done();
},
// finish handler
function () {
expectedFilenames.forEach(function (filename) {
assert.strictEqual(actualFilenames.indexOf(filename) !== -1, true,
'Actual filenames do not match expected filenames');
});
assert.strictEqual(actualFilenames.length, expectedFilenames.length,
'Actual filenames length does not match expected filenames length');
console.log('descent control OK');
checkCancel(expectedFilenames);
},
// optional scope
null,
// optional walk controller
function (dirname, dirPath, next, skip) {
dirname === 'test' ? skip() : next();
}
);
}
function checkScopes(expectedFilenames)
{
var scopedHandler = new ScopedHandler(expectedFilenames);
walkitout(startPath,
scopedHandler.handleFile,
scopedHandler.handleFinish,
scopedHandler);
}
function ScopedHandler(expectedFilenames)
{
this.expectedFilenames = expectedFilenames;
this.handleFile = function (err, fileName, done) {
var actual = this.toString();
var expected = '[ScopedHandler]';
assert.strictEqual(actual, expected,
'callback handler should retain scope: ' +
'expected "' + expected + '" not "' + actual + '"');
done();
};
this.handleFinish = function () {
var actual = this.toString();
var expected = '[ScopedHandler]';
console.log('callback handler scope OK');
console.log('descent controller scope OK');
assert.strictEqual(actual, expected,
'callback handler should retain scope: ' +
'expected "' + expected + '" not "' + actual + '"');
console.log('finish handler scope OK');
checkWalkControl(this.expectedFilenames);
};
this.handleDescent = function (dirname, dirpath, descend, skip, depth) {
var actual = this.toString();
var expected = '[ScopedHandler]';
assert.strictEqual(actual, expected,
'descent controller should retain scope: ' +
'expected "' + expected + '" not "' + actual + '"');
descend();
}
this.toString = function () {
return '[ScopedHandler]';
};
}
function checkCancel(expectedFilenames) {
var actualFilenames = [];
var cancelWalk;
var completions = 0;
cancelWalk = walkitout(startPath, function (err, filename, next) {
if (err) return next();
actualFilenames.push(filename);
next();
}, function (cancelled) {
assert.strictEqual(++completions, 1,
'Cancel should complete only once');
assert.strictEqual(true, cancelled,
'Cancel should complete with cancelled indication');
assert.notEqual(actualFilenames.length, expectedFilenames.length,
'cancelled walk should result in different length actual vs expected filenames array');
checkLateCancel(expectedFilenames);
});
cancelWalk();
cancelWalk();
}
function checkLateCancel(expectedFilenames) {
var actualFilenames = [];
var cancelWalk;
var completed = false;
cancelWalk = walkitout(startPath, function (err, filename, next) {
if (err) return next();
actualFilenames.push(filename);
next();
}, function (cancelled) {
assert.strictEqual(false, cancelled,
'Late cancel should complete normally');
});
setTimeout(function () {
cancelWalk();
console.log('cancellation OK');
}, 1000);
}