test/Validator.test.js

Summary

Maintainability
A
0 mins
Test Coverage
/**
 * The goal here is to have a minimum-viable test case here, in order to
 * check changes in Validator.
 *
 * Changes in Validator should ideally be checked to see if they trigger here.
 *
 * test-console reference: https://github.com/jamesshore/test-console
 */
var assert = require('assert');
var stdout = require('test-console').stdout;
var Validator = require("../lib/shared/Validator").default;

// Copied from lib/network/options.js
let string = 'string';
let bool = 'boolean';
let number = 'number';
let array = 'array';
let object = 'object'; // should only be in a __type__ property
let dom = 'dom';
let any = 'any';


let allOptions = {
  // simple options
  enabled: { boolean: bool },
  inherit: { string: ['from', 'to', 'both'] },
  size: { number },
  filter: { 'function': 'function' },
  chosen: {
    label: { boolean: bool },
    edge: { 'function': 'function' },
    __type__: { object }
  },
  chosen2: {
    label: { string },
    __type__: { object }
  },


  // Tests with any. These have been tailored to test all paths in:
  //   -  Validator.check()
  //   -  Validator.checkFields()
  __any__: { string },            // Any option name allowed here, but it must be a string
                                  // NOTE: you can have as many new options as you want! IS THIS INTENTIONAL?
  groups: {
    generic: { any },
    __any__: { any },
    __type__: { object }
  },


  // TODO: add combined options, e.g.
  //inherit: { string: ['from', 'to', 'both'], boolean: bool },
  //filter: { boolean: bool, string, array, 'function': 'function' },
  __type__: { object }
};


describe('Validator', function() {

  function run_validator(options, check_correct, definition = undefined) {
    let errorFound;
    let output;

    if (definition === undefined) {
      definition = allOptions;
    }

    output = stdout.inspectSync(function() {
      errorFound = Validator.validate(options, definition);
    });

    if (check_correct) {
      assert(!errorFound);
      assert(output.length === 0, 'No error expected');
    } else {
      //console.log(output); //sometimes useful here
      assert(errorFound, 'Validation should have failed');
      assert(output.length !== 0, 'must return errors');
    }

    return output;
  }


  function testExpected(output, expectedErrors) {
    for (let i = 0; i < expectedErrors.length; ++i) {
      assert(expectedErrors[i].test(output[i]), 'Regular expression at index ' + i + ' failed');
    }
    assert(output.length === expectedErrors.length, 'Number of expected errors does not match returned errors');
  }


  it('handles regular options correctly', function(done) {
    // Empty options should be accepted as well
    run_validator({}, true);

    // test values for all options
    var options = {
      enabled: true,
      inherit: 'from',
      size   : 123,
      filter : function() { return true; },
      chosen : {
        label: false,
        edge :function() { return true; },
      },
      chosen2: {
        label: "I am a string"
      },

      myNameDoesntMatter: "My type does",
      groups : {
        generic: "any type is good here",
        dontCareAboutName: [0,1,2,3]        // Type can also be anything
      }
    };

    run_validator(options, true);

    done();
  });


  it('rejects incorrect options', function(done) {
    // All of the options are wrong, all should generate an error
    var options = {
      iDontExist: 42,                       // name is 'any' but type must be string
      enabled   : 'boolean',
      inherit   : 'abc',
      size      : 'not a number',
      filter    : 42,
      chosen    : 'not an object',
      chosen2   : {
        label   : 123,

        // Following test the working of Validator.getSuggestion()
        iDontExist: 'asdf',
        generic   : "I'm not defined here",
        labe      : 42,   // Incomplete name
        labell    : 123,
      },

    };

    var output = run_validator(options, false);
    // Sometimes useful: console.log(output);

    // Errors are in the order as the options are defined in the object
    let expectedErrors = [
      /Invalid type received for "iDontExist"\. Expected: string\. Received \[number\]/,
      /Invalid type received for "enabled"\. Expected: boolean\. Received \[string\]/,
      /Invalid option detected in "inherit"\. Allowed values are:from, to, both not/,
      /Invalid type received for "size"\. Expected: number\. Received \[string\]/,
      /Invalid type received for "filter"\. Expected: function\. Received \[number\]/,
      /Invalid type received for "chosen"\. Expected: object\. Received \[string\]/,
      /Invalid type received for "label". Expected: string. Received \[number\]/,

      // Expected results of Validator.getSuggestion()
      /Unknown option detected: "iDontExist"\. Did you mean one of these:/,
      /Unknown option detected: "generic"[\s\S]*Perhaps it was misplaced\? Matching option found at:/gm,
      /Unknown option detected: "labe"[\s\S]*Perhaps it was incomplete\? Did you mean:/gm,
      /Unknown option detected: "labell"\. Did you mean "label"\?/
    ];
    testExpected(output, expectedErrors);

    done();
  });


  /**
   * Explicit tests on explicit 'undefined', to be really sure it works as expected.
   */
  it('properly handles explicit `undefined`', function(done) {
    // Option definitions with 'undefined'
    let undefinedOptions = {
      width        : { number, 'undefined': 'undefined' },
      undefOnly    : { 'undefined': 'undefined' },
      colorOptions : {
        fill       : { string },
        stroke     : { string, 'undefined': 'undefined' },
        strokeWidth: { number },
        __type__   : { string, object, 'undefined': 'undefined' }
      },
      moreOptions : {
        hello      : { string },
        world      : { string, 'undefined': 'undefined' },
        __type__   : { object }
      }
    }

    //
    // Test good actual option values
    //
    let correct1 = {
      width       : 42,
      colorOptions: 'I am a string',
      moreOptions : {
        hello: 'world',
        world: '!'
            }
    }
    var output = run_validator(correct1, true, undefinedOptions);

    let correct2 = {
      width       : undefined,
      colorOptions: {
        fill  : 'I am a string',
        stroke: 'I am a string'
      },
      moreOptions : {
        world: undefined 
            }
    }
    var output = run_validator(correct2, true, undefinedOptions);

    let correct3 = {
      width       : undefined,
      undefOnly   : undefined,
      colorOptions: undefined
    }
    var output = run_validator(correct3, true, undefinedOptions);

    //
    // Test bad actual option values
    //
    let bad1 = {
      width       : 'string',
      undefOnly   : 42,
      colorOptions: 42,
      moreOptions : undefined
    }
    var output = run_validator(bad1, false, undefinedOptions);

    let expectedErrors = [
      /Invalid type received for "width"\. Expected: number, undefined\. Received \[string\]/,
      /Invalid type received for "undefOnly"\. Expected: undefined\. Received \[number\]/,
      /Invalid type received for "colorOptions"\. Expected: string, object, undefined\. Received \[number\]/,
      /Invalid type received for "moreOptions"\. Expected: object\. Received \[undefined\]/
    ];
    testExpected(output, expectedErrors);

    let bad2 = {
      undefOnly   : 'undefined',
      colorOptions: {
        fill: undefined
      } ,
      moreOptions: {
        hello: undefined,
        world: 42
      } 
    }
    var output = run_validator(bad2, false, undefinedOptions);

    let expectedErrors2= [
      /Invalid type received for "undefOnly"\. Expected: undefined\. Received \[string\]/,
      /Invalid type received for "fill"\. Expected: string\. Received \[undefined\]/,
      /Invalid type received for "hello"\. Expected: string\. Received \[undefined\]/,
      /Invalid type received for "world"\. Expected: string, undefined\. Received \[number\]/
    ];
    testExpected(output, expectedErrors2);

    done();
  });
});