text: var assert = require('assert');
  var jmespath = require('../jmespath');
  var tokenize = jmespath.tokenize;
  var compile = jmespath.compile;
  var strictDeepEqual = jmespath.strictDeepEqual;
  
  
  describe('tokenize', function() {
      it('should tokenize unquoted identifier', function() {
          assert.deepEqual(tokenize('foo'),
                           [{type: "UnquotedIdentifier",
                            value: "foo",
                            start: 0}]);
      });
      it('should tokenize unquoted identifier with underscore', function() {
          assert.deepEqual(tokenize('_underscore'),
                            [{type: "UnquotedIdentifier",
                             value: "_underscore",
                             start: 0}]);
      });
      it('should tokenize unquoted identifier with numbers', function() {
          assert.deepEqual(tokenize('foo123'),
                            [{type: "UnquotedIdentifier",
                             value: "foo123",
                             start: 0}]);
      });
      it('should tokenize dotted lookups', function() {
          assert.deepEqual(
              tokenize('foo.bar'),
              [{type: "UnquotedIdentifier", value: "foo", start: 0},
               {type: "Dot", value: ".", start: 3},
               {type: "UnquotedIdentifier", value: "bar", start: 4},
              ]);
      });
      it('should tokenize numbers', function() {
          assert.deepEqual(
              tokenize('foo[0]'),
              [{type: "UnquotedIdentifier", value: "foo", start: 0},
               {type: "Lbracket", value: "[", start: 3},
               {type: "Number", value: 0, start: 4},
               {type: "Rbracket", value: "]", start: 5},
              ]);
      });
      it('should tokenize numbers with multiple digits', function() {
          assert.deepEqual(
              tokenize("12345"),
              [{type: "Number", value: 12345, start: 0}]);
      });
      it('should tokenize negative numbers', function() {
          assert.deepEqual(
              tokenize("-12345"),
              [{type: "Number", value: -12345, start: 0}]);
      });
      it('should tokenize quoted identifier', function() {
          assert.deepEqual(tokenize('"foo"'),
                           [{type: "QuotedIdentifier",
                            value: "foo",
                            start: 0}]);
      });
      it('should tokenize quoted identifier with unicode escape', function() {
          assert.deepEqual(tokenize('"\\u2713"'),
                           [{type: "QuotedIdentifier",
                            value: "✓",
                            start: 0}]);
      });
      it('should tokenize literal lists', function() {
          assert.deepEqual(tokenize("`[0, 1]`"),
                           [{type: "Literal",
                            value: [0, 1],
                            start: 0}]);
      });
      it('should tokenize literal dict', function() {
          assert.deepEqual(tokenize("`{\"foo\": \"bar\"}`"),
                           [{type: "Literal",
                            value: {"foo": "bar"},
                            start: 0}]);
      });
      it('should tokenize literal strings', function() {
          assert.deepEqual(tokenize("`\"foo\"`"),
                           [{type: "Literal",
                            value: "foo",
                            start: 0}]);
      });
      it('should tokenize json literals', function() {
          assert.deepEqual(tokenize("`true`"),
                           [{type: "Literal",
                            value: true,
                            start: 0}]);
      });
      it('should not requiring surrounding quotes for strings', function() {
          assert.deepEqual(tokenize("`foo`"),
                           [{type: "Literal",
                            value: "foo",
                            start: 0}]);
      });
      it('should not requiring surrounding quotes for numbers', function() {
          assert.deepEqual(tokenize("`20`"),
                           [{type: "Literal",
                             value: 20,
                             start: 0}]);
      });
      it('should tokenize literal lists with chars afterwards', function() {
          assert.deepEqual(
              tokenize("`[0, 1]`[0]"), [
                  {type: "Literal", value: [0, 1], start: 0},
                  {type: "Lbracket", value: "[", start: 8},
                  {type: "Number", value: 0, start: 9},
                  {type: "Rbracket", value: "]", start: 10}
          ]);
      });
      it('should tokenize two char tokens with shared prefix', function() {
          assert.deepEqual(
              tokenize("[?foo]"),
              [{type: "Filter", value: "[?", start: 0},
               {type: "UnquotedIdentifier", value: "foo", start: 2},
               {type: "Rbracket", value: "]", start: 5}]
          );
      });
      it('should tokenize flatten operator', function() {
          assert.deepEqual(
              tokenize("[]"),
              [{type: "Flatten", value: "[]", start: 0}]);
      });
      it('should tokenize comparators', function() {
          assert.deepEqual(tokenize("<"),
                           [{type: "LT",
                            value: "<",
                            start: 0}]);
      });
      it('should tokenize two char tokens without shared prefix', function() {
          assert.deepEqual(
              tokenize("=="),
              [{type: "EQ", value: "==", start: 0}]
          );
      });
      it('should tokenize not equals', function() {
          assert.deepEqual(
              tokenize("!="),
              [{type: "NE", value: "!=", start: 0}]
          );
      });
      it('should tokenize the OR token', function() {
          assert.deepEqual(
              tokenize("a||b"),
              [
                  {type: "UnquotedIdentifier", value: "a", start: 0},
                  {type: "Or", value: "||", start: 1},
                  {type: "UnquotedIdentifier", value: "b", start: 3}
              ]
          );
      });
      it('should tokenize function calls', function() {
          assert.deepEqual(
              tokenize("abs(@)"),
              [
                  {type: "UnquotedIdentifier", value: "abs", start: 0},
                  {type: "Lparen", value: "(", start: 3},
                  {type: "Current", value: "@", start: 4},
                  {type: "Rparen", value: ")", start: 5}
              ]
          );
      });
  
  });
  
  
  describe('parsing', function() {
      it('should parse field node', function() {
          assert.deepEqual(compile('foo'),
                            {type: 'Field', name: 'foo'});
      });
  });
  
  describe('strictDeepEqual', function() {
      it('should compare scalars', function() {
          assert.strictEqual(strictDeepEqual('a', 'a'), true);
      });
      it('should be false for different types', function() {
          assert.strictEqual(strictDeepEqual('a', 2), false);
      });
      it('should be false for arrays of different lengths', function() {
          assert.strictEqual(strictDeepEqual([0, 1], [1, 2, 3]), false);
      });
      it('should be true for identical arrays', function() {
          assert.strictEqual(strictDeepEqual([0, 1], [0, 1]), true);
      });
      it('should be true for nested arrays', function() {
          assert.strictEqual(
              strictDeepEqual([[0, 1], [2, 3]], [[0, 1], [2, 3]]), true);
      });
      it('should be true for nested arrays of strings', function() {
          assert.strictEqual(
              strictDeepEqual([["a", "b"], ["c", "d"]],
                              [["a", "b"], ["c", "d"]]), true);
      });
      it('should be false for different arrays of the same length', function() {
          assert.strictEqual(strictDeepEqual([0, 1], [1, 2]), false);
      });
      it('should handle object literals', function() {
          assert.strictEqual(strictDeepEqual({a: 1, b: 2}, {a: 1, b: 2}), true);
      });
      it('should handle keys in first not in second', function() {
          assert.strictEqual(strictDeepEqual({a: 1, b: 2}, {a: 1}), false);
      });
      it('should handle keys in second not in first', function() {
          assert.strictEqual(strictDeepEqual({a: 1}, {a: 1, b: 2}), false);
      });
      it('should handle nested objects', function() {
          assert.strictEqual(
              strictDeepEqual({a: {b: [1, 2]}},
                              {a: {b: [1, 2]}}), true);
      });
      it('should handle nested objects that are not equal', function() {
          assert.strictEqual(
              strictDeepEqual({a: {b: [1, 2]}},
                              {a: {b: [1, 4]}}), false);
      });
  });
  
  describe('search', function() {
      it(
          'should throw a readable error when invalid arguments are provided to a function',
          function() {
              try {
                  jmespath.search([], 'length(`null`)');
              } catch (e) {
                  assert(e.message.search(
                      'expected argument 1 to be type string,array,object'
                  ), e.message);
                  assert(e.message.search('received type null'), e.message);
              }
          }
      );
  });
  
  describe('decorate', function() {
    it(
      'should call a custom function when called via decorator',
      function() {
        var TYPE_NUMBER = 0;
        function customFunc(resolvedArgs) {
          return resolvedArgs[0] + 99;
        }
        var extraFunctions = {
          custom: {_func: customFunc, _signature: [{types: [TYPE_NUMBER]}]},
        };
        var value = jmespath.decorate(extraFunctions)('custom(`1`)')({});
        assert.strictEqual(value, 100);
      }
    );
    it(
      'should provide a compiled expression that can be cached and reused',
      function() {
        var expr = jmespath.decorate({})('a');
        var value = expr({ a: 1 });
        assert.strictEqual(value, 1);
        value = expr({ a: 2 });
        assert.strictEqual(value, 2);
      }
    );
  });
  
  describe('root', function() {
    it(
      '$ should give access to the root value',
      function() {
        var value = jmespath.search({ foo: { bar: 1 }}, 'foo.{ value: $.foo.bar }');
        assert.equal(value.value, 1);
      }
    );
    it(
      '$ should give access to the root value after pipe',
      function() {
        var value = jmespath.search({ foo: { bar: 1 }}, 'foo | $.foo.bar');
        assert.strictEqual(value, 1);
      }
    );
  });
  
  describe('key only projection', function () {
    it('should be able to form multi-select hash using only the keys', function () {
      var data = { one: 'first',  two: 'second' };
      var result = jmespath.search(data, '{ one }');
      strictDeepEqual(result, { one: 'first' });
    });
  });
  
  describe('optional function arg', function () {
    it('should allow final args to be declared as optional', function () {
      var TYPE_NUMBER = 0;
      var wasCalled = false;
      function customFunc(resolvedArgs) {
        assert.strictEqual(resolvedArgs[1], undefined);
        assert.strictEqual(resolvedArgs[2], undefined);
        wasCalled = true;
        return resolvedArgs[0];
      }
      var extraFunctions = {
        custom: {
          _func: customFunc,
          _signature: [{types: [TYPE_NUMBER]},{types: [TYPE_NUMBER], optional: true},{types: [TYPE_NUMBER], optional: true}]
        },
      };
      var value = jmespath.decorate(extraFunctions)('custom(`1`)')({});
      assert.strictEqual(wasCalled, true);
      assert.strictEqual(value, 1);
    });
    it('should allow passing optional args', function () {
      var TYPE_NUMBER = 0;
      var wasCalled = false;
      function customFunc(resolvedArgs) {
        wasCalled = true;
        return resolvedArgs[0] + (resolvedArgs[1] || 0) + (resolvedArgs[2] || 0);
      }
      var extraFunctions = {
        custom: {
          _func: customFunc,
          _signature: [
            {types: [TYPE_NUMBER]},
            {types: [TYPE_NUMBER], optional: true},
            {types: [TYPE_NUMBER], optional: true}
          ]
        },
      };
      var value = jmespath.decorate(extraFunctions)('custom(`1`, `2`)')({});
      assert.strictEqual(wasCalled, true);
      assert.strictEqual(value, 3);
      wasCalled = false;
      value = jmespath.decorate(extraFunctions)('custom(`1`, `2`, `3`)')({});
      assert.strictEqual(wasCalled, true);
      assert.strictEqual(value, 6);
    });
  });
  