Home Reference Source Test

spec/nodeUtilsSpec.js

var expect    = require('expect.js');
var fixtures  = require('./fixtures');
var helpers   = require('./helpers');
var NodeUtils = require('../lib/nodeUtils');

describe('NodeUtils', function() {
  describe('walkSubtrees', function() {
    it('walks each child node using DFS', function() {
      var res = [];
      var root = helpers.parse(fixtures.simple)[0];
      NodeUtils.walkSubtrees(root, node => res.push(node.type));
      expect(res).to.eql([
        'ExpressionStatement',
        'UpdateExpression',
        'Identifier',
        'ExpressionStatement',
        'CallExpression',
        'Identifier'
      ]);
    });
  });

  describe('getDFSTraversal', function() {
    it('returns the DFS traversal of the AST', function() {
      var res = [];
      var ast = helpers.parse(fixtures.simple)[0];
      var res = NodeUtils.getDFSTraversal(ast).map(node => node.type);
      expect(res).to.eql([
        'BlockStatement',
        'ExpressionStatement',
        'UpdateExpression',
        'Identifier',
        'ExpressionStatement',
        'CallExpression',
        'Identifier'
      ]);
    });
  });

  describe('getBFSTraversal', function() {
    it('returns the BFS traversal of the AST', function() {
      var res = [];
      var ast = helpers.parse(fixtures.simple)[0];
      var res = NodeUtils.getBFSTraversal(ast).map(node => node.type);
      expect(res).to.eql([
        'BlockStatement',
        'ExpressionStatement',
        'ExpressionStatement',
        'UpdateExpression',
        'CallExpression',
        'Identifier',
        'Identifier'
      ]);
    });
  });

  describe('getChildren', function() {
    it('returns the children of a Node', function() {
      var parent = helpers.parse(fixtures.intersection)[0];
      var res = NodeUtils.getChildren(parent);
      // Children should be the three identifiers intersectionA,
      // array1, and array2, followed by a block statement
      expect(res.map(node => node.type)).to.eql([
        'Identifier',
        'Identifier',
        'Identifier',
        'BlockStatement'
      ])
    });

    it('ignores null children', function() {
      var parent = helpers.parse(fixtures.nullChildren)[1].expression.left;
      // parent.elements is an array with a leading null that should be ignored,
      // followed by an identifier
      var res = NodeUtils.getChildren(parent);
      expect(res).to.have.length(1);
      expect(res[0].type).to.be('Identifier');
    });

    it('ignores empty JSXText nodes', function() {
      var parent = helpers.parse(fixtures.jsxNesting)[0].expression;
      var res = NodeUtils.getChildren(parent);
      res.forEach(node => expect(node.type).not.to.be('JSXText'));
    });
  });

  describe('isBefore', function() {
    describe('given nodes with different line numbers', function() {
      it('returns true if the first node has a lower line number', function() {
        var res = NodeUtils.isBefore(
          {loc: {start: {line: 0, column: 0}}},
          {loc: {start: {line: 1, column: 0}}}
        );
        expect(res).to.be(true);
      });

      it('returns false if the first node has a higher numbered line', function() {
        var res = NodeUtils.isBefore(
          {loc: {start: {line: 1, column: 0}}},
          {loc: {start: {line: 0, column: 0}}}
        );
        expect(res).to.be(false);
      });
    });

    describe('given nodes with the same line number', function() {
      it('returns true if the first node has a lower column number', function() {
        var res = NodeUtils.isBefore(
          {loc: {start: {line: 0, column: 0}}},
          {loc: {start: {line: 0, column: 1}}}
        );
        expect(res).to.be(true);
      });

      it('returns false if the first node has a higher column number', function() {
        var res = NodeUtils.isBefore(
          {loc: {start: {line: 0, column: 1}}},
          {loc: {start: {line: 0, column: 0}}}
        );
        expect(res).to.be(false);
      });
    });
  });

  describe('isES6ModuleImport', function() {
    it('returns true for an import declaration', function() {
      // ImportDeclaration
      var nodes = [helpers.parse(fixtures.es6Module)[0]];
      expect(NodeUtils.isES6ModuleImport(nodes)).to.be(true);
    });

    it('returns false for export declaration', function() {
      // ExportNamedDeclaration
      var nodes = [helpers.parse(fixtures.es6Module)[1]];
      expect(NodeUtils.isES6ModuleImport(nodes)).to.be(false);
    });

    it('returns false otherwise', function() {
      var nodes = helpers.parse(fixtures.commonjs);
      expect(NodeUtils.isES6ModuleImport(nodes)).to.be(false);
    });
  });

  describe('isAMD', function() {
    it('returns true for an expression containing a define', function() {
      // First expression is define
      var nodes = [helpers.parse(fixtures.amd)[0]];
      expect(NodeUtils.isAMD(nodes)).to.be(true);
    });

    it('returns true for an expression containing a define', function() {
      // Third expression is require
      var nodes = [helpers.parse(fixtures.amd)[2]];
      expect(NodeUtils.isAMD(nodes)).to.be(true);
    });

    it('returns true even if the function is a property', function() {
      var nodes = [helpers.parse(fixtures.amd)[4]];
      expect(NodeUtils.isAMD(nodes)).to.be(true);
    });

    it('returns true even if a nested property', function() {
      var nodes = [helpers.parse(fixtures.amd)[6]];
      expect(NodeUtils.isAMD(nodes)).to.be(true);
    });

    it('returns false otherwise', function() {
      var nodes = helpers.parse(fixtures.commonjs);
      expect(NodeUtils.isAMD(nodes)).to.be(false);
    });
  });

  describe('isCommonJS', function() {
    it('returns true for an expression containing a require', function() {
      // First node is an ExpressionStatement
      var nodes = [helpers.parse(fixtures.commonjs)[0]];
      expect(NodeUtils.isCommonJS(nodes)).to.be(true);
    });

    it('returns true for a declaration containing a require', function() {
      // Second node is a VariableDeclaration
      var nodes = [helpers.parse(fixtures.commonjs)[1]];
      expect(NodeUtils.isCommonJS(nodes)).to.be(true);
    });

    it('returns false otherwise', function() {
      var nodes = helpers.parse(fixtures.amd);
      expect(NodeUtils.isCommonJS(nodes)).to.be(false);
    });
  });

  describe('typesMatch', function() {
    it('returns true if all node types match', function() {
      var res = NodeUtils.typesMatch([
        {type: 'a'}, {type: 'a'}, {type: 'a'}
      ]);
      expect(res).to.be(true);
    });

    it('returns false if not all node types match', function() {
      var res = NodeUtils.typesMatch([
        {type: 'a'}, {type: 'a'}, {type: 'b'}
      ]);
      expect(res).to.be(false);
    });
  });

  describe('identifiersMatch', function() {
    it('returns true if all node are matching identifiers', function() {
      var res = NodeUtils.identifiersMatch([
        {name: 'a'}, {name: 'a'}, {name: 'a'}
      ]);
      expect(res).to.be(true);
    });

    it('returns false if not all node names match', function() {
      var res = NodeUtils.identifiersMatch([
        {name: 'a'}, {name: 'a'}, {name: 'b'}
      ]);
      expect(res).to.be(false);
    });
  });

  describe('literalsMatch', function() {
    it('returns true if all literals have the same value', function() {
      var res = NodeUtils.literalsMatch([
        {type: 'Literal', value: 'a'},
        {type: 'Literal', value: 'a'},
        {type: 'Literal', value: 'a'}
      ]);
      expect(res).to.be(true);
    });

    it('returns false if not all literals have the same value', function() {
      var res = NodeUtils.literalsMatch([
        {type: 'Literal', value: 'a'},
        {type: 'Literal', value: 'a'},
        {type: 'Literal', value: 'b'}
      ]);
      expect(res).to.be(false);
    });

    it('treats JSXText as a literal', function() {
      var res = NodeUtils.literalsMatch([
        {type: 'JSXText', value: 'a'},
        {type: 'JSXText', value: 'a'},
        {type: 'JSXText', value: 'b'}
      ]);
      expect(res).to.be(false);
    });

    it('ignores the values of nodes which are not literals', function() {
      var res = NodeUtils.literalsMatch([
        {type: 'Foo', value: 'a'},
        {type: 'Foo', value: 'a'},
        {type: 'Foo', value: 'b'}
      ]);
      expect(res).to.be(true);
    });
  });
});