henrikrudstrom/oce-wrap

View on GitHub
src/testLib.js

Summary

Maintainability
A
30 mins
Test Coverage
var common = require('./common.js');
var settings = require('./settings.js');
var modules; // intialized on demand;

const glob = require('glob');
const fs = require('fs');
const path = require('path');
const yargs = require('yargs');
const camelCase = require('camel-case');

// Parse specs in module folder to remove tests that are manually defined
// TODO: find a package for this or make it explicit
function parseTests() {
  var specs = {};
  glob.sync(`${settings.paths.definition}/spec/**/*Spec.js`)
    .concat(glob.sync(`${settings.paths.definition}/spec/*Spec.js`))
    .forEach(file => {
      var itexp = / *x?it\('(\w+\(.*\))',((?:.|\n)*?\}\));/g;
      var descExp = /describe\('((?:\w|\.|_| )*)',((?:.|\n)*?}\)(\n| |;)*}\)?)/g;
      var src = fs.readFileSync(file).toString();
      var matches = src.match(descExp);
      if (matches)
        matches.forEach(desc => {
          var suite = desc.match(/describe\('(.+)'/)[1];
          var smatches = desc.match(itexp);
          if (smatches)
            smatches.forEach(it => {
              var name = it.match(/it\('(\w+\(.*\))'/)[1];
              if (!Array.isArray(specs[suite]))
                specs[suite] = [];
              specs[suite].push(name);
            });
        });
    });
  return specs;
}


var overridenTests = parseTests();

function isOverriden(suite, spec) {
  if (suite in overridenTests) {
    return overridenTests[suite].some(ignore => common.match(ignore, spec));
    //return overridenTests[suite].indexOf(spec) !== -1;
  }
  return false;
}

var specOverridePath = path.join(settings.paths.definition, 'spec', 'notWorking.js');
var specOverride = fs.existsSync(specOverridePath) ?
  require(path.relative(__dirname, specOverridePath)) : {
    notWorking() {
      return false;
    },
    returnType() {
      return false;
    }
  };



var next = 0;

function int() {
  next += 1;
  return Math.round(next); // nextInt.toString();
}

function double() {
  next += 0.5;
  return next;
}

function bool() {
  next += 1;
  return Boolean(!Math.round(next) % 2);
}

function resetNumbers() {
  next = 0;
}

function createValue(typeName, index) {
  if (typeName === 'Integer') return int();
  if (typeName === 'Double') return double();
  if (typeName === 'Boolean') return bool();
  if (typeName === 'String') return '';
  var mod = typeName.split('.')[0];
  var type = typeName.split('.')[1] || typeName;
  // if (!mod || !type) throw new Error(typeName)
  type = type.slice(0, 1).toLowerCase() + type.slice(1);
  return `create.${mod}.${type}()`;
}
module.exports.createValue = createValue;


// check if all arguments / return types are wrapped, otherwise dont test it.
function requiredTypesWrapped(member) {
  modules = modules || require('./modules.js')();

  var unwrapped = member.arguments ? member.arguments.map(arg => arg.type) : [];
  if (member.declType !== 'constructor')
    unwrapped.push(member.returnType || member.type);

  var res = unwrapped.every(
    type => type === 'void' || type === 'Array' || type === 'Object' || modules.get(type)
  );

  return res;
}


function excluded(qualifiedClsName, member) {
  var signature = common.signature(member);

  if (isOverriden(qualifiedClsName, signature)) {
    return signature + ' Redefined.';
  }

  return false;
}

function pending(qualifiedClsName, member) {
  var signature = common.signature(member);

  if (!requiredTypesWrapped(member))
    return 'Arguments or return type not wrapped.';

  if (specOverride.notWorking(qualifiedClsName, signature)) {
    return 'TODO: ' + signature + 'not working.';
  }
  return false;
}

function expectType(returnType) {
  if (returnType === 'Integer' || returnType === 'Double')
    return ['expect(typeof res).to.equal(\'number\');'];
  else if (returnType === 'Boolean')
    return ['expect(typeof res).to.equal(\'boolean\');'];
  else if (returnType !== 'void')
    return [
      `helpers.expectType(res, '${returnType}');`
    ];
  return [];
}


function memberReturnType(cls, member, suiteKey) {
  if (member.declType === 'constructor') return cls.parent + '.' + cls.name;
  modules = modules || require('./modules.js')();
  var rtype = (member.returnType || member.type);
  var type = modules.get(rtype);
  if (type && type.declType === 'enum') return 'Integer';
  var returnType = rtype;
  if (member.downCastToThis)
    returnType = cls.parent + '.' + cls.name;


  var override = specOverride.returnType(suiteKey, common.signature(member));
  if (override)
    returnType = override;
  return returnType;
}

function renderPendingTest(signature, comment) {
  return `
  // ${comment}
  xit('${signature}', function() { });`;
}

function renderTest(member, testSrc, parts) {
  var cls = member.getParent();
  var signature = common.signature(member);

  var excludedReason = excluded(cls.qualifiedName, member);
  if (excludedReason)
    return '// ${excludedReason}';

  var pendingReason = pending(cls.qualifiedName, member);
  if (pendingReason)
    return renderPendingTest(signature, pendingReason);


  if (member.declType !== 'constructor' || member.declType !== 'property') {
    var returnType = memberReturnType(cls, member, cls.qualifiedName);
    testSrc += expectType(returnType).map(l => '\n    ' + l).join('');
  }
  testSrc += parts.get(cls.name + '.' + signature + 'Expectations');
  var consolelog = yargs.argv.logspec ? `    console.log('${signature}')\n` : '';
  var src = `
  it('${signature}', function() {
${consolelog}${testSrc}
  });`;

  resetNumbers();

  return src;
}

function classHierarchy(configModules) {
  var inherited = {};
  configModules.forEach(configPath => {
    var mod = JSON.parse(fs.readFileSync(configPath));
    mod.declarations.filter(decl => decl.declType === 'class').forEach(cls => {
      cls.bases.forEach(base => {
        if (!inherited.hasOwnProperty(base.name))
          inherited[base.name] = [];
        inherited[base.name].push(mod.name + '.' + cls.name);
      });
    });
  });
  return inherited;
}


module.exports = {
  pending,
  excluded,
  memberReturnType,
  expectType,
  createValue,
  renderTest,
  resetNumbers,
  classHierarchy
};