const {assert} = require('chai');
let Parser, JavaScript;

describe("Query", () => {
  let parser, tree, query;

  before(async () =>
    ({Parser, JavaScript} = await require('./helper'))
  );

  beforeEach(() => {
    parser = new Parser().setLanguage(JavaScript);
  });

  afterEach(() => {
    parser.delete();
    if (tree) tree.delete();
    if (query) query.delete();
  });

  describe('construction', () => {
    it('throws an error on invalid patterns', () => {
      assert.throws(() => {
        JavaScript.query("(function_declaration wat)")
      }, "Bad syntax at offset 22: \'wat)\'...");
      assert.throws(() => {
        JavaScript.query("(non_existent)")
      }, "Bad node name 'non_existent'");
      assert.throws(() => {
        JavaScript.query("(a)")
      }, "Bad node name 'a'");
      assert.throws(() => {
        JavaScript.query("(function_declaration non_existent:(identifier))")
      }, "Bad field name 'non_existent'");
    });

    it('throws an error on invalid predicates',  () => {
      assert.throws(() => {
        JavaScript.query("((identifier) @abc (eq? @ab hi))")
      }, "Bad capture name @ab");
      assert.throws(() => {
        JavaScript.query("((identifier) @abc (eq? @ab hi))")
      }, "Bad capture name @ab");
      assert.throws(() => {
        JavaScript.query("((identifier) @abc (eq?))")
      }, "Wrong number of arguments to `eq?` predicate. Expected 2, got 0");
      assert.throws(() => {
        JavaScript.query("((identifier) @a (eq? @a @a @a))")
      }, "Wrong number of arguments to `eq?` predicate. Expected 2, got 3");
      assert.throws(() => {
        JavaScript.query("((identifier) @a (something-else? @a))")
      }, "Unknown query predicate `something-else?`");
    });
  });

  describe('.matches', () => {
    it('returns all of the matches for the given query',  () => {
      tree = parser.parse("function one() { two(); function three() {} }");
      query = JavaScript.query(`
        (function_declaration name:(identifier) @fn-def)
        (call_expression function:(identifier) @fn-ref)
      `);
      const matches = query.matches(tree.rootNode);
      assert.deepEqual(
        formatMatches(matches),
        [
          {pattern: 0, captures: [{name: 'fn-def', text: 'one'}]},
          {pattern: 1, captures: [{name: 'fn-ref', text: 'two'}]},
          {pattern: 0, captures: [{name: 'fn-def', text: 'three'}]},
        ]
      );
    });

    it('can search in a specified ranges',  () => {
      tree = parser.parse("[a, b,\nc, d,\ne, f,\ng, h]");
      query = JavaScript.query('(identifier) @element');
      const matches = query.matches(
        tree.rootNode,
        {row: 1, column: 1},
        {row: 3, column: 1}
      );
      assert.deepEqual(
        formatMatches(matches),
        [
          {pattern: 0, captures: [{name: 'element', text: 'd'}]},
          {pattern: 0, captures: [{name: 'element', text: 'e'}]},
          {pattern: 0, captures: [{name: 'element', text: 'f'}]},
          {pattern: 0, captures: [{name: 'element', text: 'g'}]},
        ]
      );
    });
  });

  describe('.captures', () => {
    it('returns all of the captures for the given query, in order', () => {
      tree = parser.parse(`
        a({
          bc: function de() {
            const fg = function hi() {}
          },
          jk: function lm() {
            const no = function pq() {}
          },
        });
      `);
      query = JavaScript.query(`
        (pair
          key: * @method.def
          (function
            name: (identifier) @method.alias))

        (variable_declarator
          name: * @function.def
          value: (function
            name: (identifier) @function.alias))

        ":" @delimiter
        "=" @operator
      `);

      const captures = query.captures(tree.rootNode);
      assert.deepEqual(
        formatCaptures(captures),
        [
          {name: "method.def", text: "bc"},
          {name: "delimiter", text: ":"},
          {name: "method.alias", text: "de"},
          {name: "function.def", text: "fg"},
          {name: "operator", text: "="},
          {name: "function.alias", text: "hi"},
          {name: "method.def", text: "jk"},
          {name: "delimiter", text: ":"},
          {name: "method.alias", text: "lm"},
          {name: "function.def", text: "no"},
          {name: "operator", text: "="},
          {name: "function.alias", text: "pq"},
        ]
      );
    });

    it('handles conditions that compare the text of capture to literal strings',  () => {
      tree = parser.parse(`
        const ab = require('./ab');
        new Cd(EF);
      `);

      query = JavaScript.query(`
        (identifier) @variable

        ((identifier) @function.builtin
         (eq? @function.builtin "require"))

        ((identifier) @constructor
         (match? @constructor "^[A-Z]"))

        ((identifier) @constant
         (match? @constant "^[A-Z]{2,}$"))
      `);

      const captures = query.captures(tree.rootNode);
      assert.deepEqual(
        formatCaptures(captures),
        [
          {name: "variable", text: "ab"},
          {name: "variable", text: "require"},
          {name: "function.builtin", text: "require"},
          {name: "variable", text: "Cd"},
          {name: "constructor", text: "Cd"},
          {name: "variable", text: "EF"},
          {name: "constructor", text: "EF"},
          {name: "constant", text: "EF"},
        ]
      );
    });

    it('handles conditions that compare the text of capture to each other',  () => {
      tree = parser.parse(`
        const ab = abc + 1;
        const def = de + 1;
        const ghi = ghi + 1;
      `);

      query = JavaScript.query(`
        ((variable_declarator
            name: (identifier) @id1
            value: (binary_expression
              left: (identifier) @id2))
         (eq? @id1 @id2))
      `);

      const captures = query.captures(tree.rootNode);
      assert.deepEqual(
        formatCaptures(captures),
        [
          {name: "id1", text: "ghi"},
          {name: "id2", text: "ghi"},
        ]
      );
    });
  });
});

function formatMatches(matches) {
  return matches.map(({pattern, captures}) => ({
    pattern,
    captures: formatCaptures(captures)
  }))
}

function formatCaptures(captures) {
  return captures.map(({name, node}) => ({ name, text: node.text }))
}

@@ -5569,30 +5569,24 @@
 e(%60%0A        
-const 
 ab = abc + 1
@@ -5595,22 +5595,16 @@
         
-const 
 def = de
@@ -5617,22 +5617,16 @@
         
-const 
 ghi = gh
@@ -5686,35 +5686,37 @@
       ((
-variable_declarator
+assignment_expression
 %0A       
@@ -5712,36 +5712,36 @@
 ion%0A            
-name
+left
 : (identifier) @
@@ -5756,21 +5756,21 @@
         
-value
+right
 : (binar

Use a more realistic example in one JS query unit test