lib/walker/walker.js
const Mocha = require('mocha');
const fs = require('fs');
const path = require('path');
class Walker {
constructor(config){
this.config = config;
this.mocha = new Mocha(Object.assign({}, config.mocha, { timeout: config.mocha.timeout * 2 }));
if(this.config.screenshotsDir){
fs.mkdirSync(path.join(this.config.screenshotsDir, 'actual'), { recursive: true });
fs.mkdirSync(path.join(this.config.screenshotsDir, 'expected'), { recursive: true });
}
if(this.config.scenarioFile){
let scenarioFiles = Walker.toArray(this.config.scenarioFile);
scenarioFiles.sort().forEach((file)=>{ this.mocha.addFile(file) })
}else{
let scenarioDirs = Walker.toArray(this.config.scenarioDir);
let ignoreDirs = Walker.toArray(this.config.ignoreDir);
scenarioDirs.map(dir => path.join(process.cwd(), dir)).forEach((scenarioDir)=>{
if(!Walker.isDir(scenarioDir)){
throw `Scenario Directory(${scenarioDir}) is not found or not directory!`;
}
Walker.findAllFilesSync(scenarioDir, ignoreDirs)
.filter(file => file.substr(-3) === '.js')
.forEach((file)=>{ this.mocha.addFile(file) })
})
}
this.loadFiles();
}
walk(callback){
this.mocha.run(callback);
}
static isDir(path){
let stat = fs.statSync(path);
return stat && stat.isDirectory();
}
static findAllFilesSync(dir, ignorePattern){
let results = [];
fs.readdirSync(dir)
.filter(file => !ignorePattern.includes(file))
.map(file => path.join(dir, file))
.forEach((file)=>{
if (this.isDir(file)){
results = results.concat(Walker.findAllFilesSync(file, ignorePattern))
}else{
results.push(file)
}
})
return results
}
static toArray(value){
return (value instanceof Array) ? value : [ value ];
}
/**
* @private
*/
loadFiles(){
let originalFunctions = {};
let delayedFunctions = [];
this.mocha.suite.on('pre-require', (context)=>{
originalFunctions.describe = context.describe;
originalFunctions.xdescribe = context.xdescribe;
originalFunctions.describeOnly = context.describe.only;
// override
context.describe = function(title, fn, ...args){
delayedFunctions.push(["describe", this, title, fn, args]);
}
context.xdescribe = function(title, fn, ...args){
delayedFunctions.push(["xdescribe", this, title, fn, args]);
}
context.describe.only = function(title, fn, ...args){
delayedFunctions.push(["describeOnly", this, title, fn, args]);
}
// set to aliases
context.context = context.suite = context.describe;
context.xcontext = context.describe.skip = context.xdescribe;
})
this.mocha.suite.on('post-require', (context)=>{
/* Do not support! **
// restore desctibe, xdescribe, describe.only with object property function support
// object property function support is describe("title", { "title": function(){...} }, ...args)
["describe", "xdescribe", "describeOnly"].forEach((key)=>{
originalFunctions[key] = Walker.objectPropertySupport(originalFunctions[key]);
})
*/
// Revert override.
context.context = context.suite = context.describe = originalFunctions.describe;
context.xcontext = context.xdescribe = originalFunctions.xdescribe;
context.describe.only = originalFunctions.describeOnly;
/* Do not support! **
// hook function allows object property support.
["before", "after", "beforeEach", "afterEach"].filter(key => context[key]).forEach((key)=>{
context[key] = Walker.objectPropertySupport(context[key]);
})
*/
})
this.mocha.loadFiles();
// Delayed evaluation.
delayedFunctions.forEach((array)=>{
let func = originalFunctions[array[0]];
func.call(array[1], array[2], array[3], ...array[4]); // this, title, fn, args
})
}
/**
* @deprecated
* @private
*/
static objectPropertySupport(originalFunc){
let newFunc = function(title, fn, ...args){
if(typeof title == "function"){
originalFunc.call(this, title);
}
if(typeof fn == "function"){
originalFunc.call(this, title, fn);
}
if(typeof fn == "object"){
if(typeof fn[title] != "function") throw new Error(`Property missing! "${title}" on object [${fn}]`)
originalFunc.call(this, title, function(){ return fn[title].call(this, ...args) });
}
};
for(var prop in originalFunc){
if(originalFunc.hasOwnProperty(prop)) newFunc[prop] = originalFunc[prop];
}
return newFunc;
}
}
module.exports = Walker;