Started test suite for query creator

Change-Id: I320473cb80c7528c19a44d84e56cbc3c635a0bf1
diff --git a/dev/js/runner/querycreator.html b/dev/js/runner/querycreator.html
new file mode 100644
index 0000000..dacfaa4
--- /dev/null
+++ b/dev/js/runner/querycreator.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Spec Runner for Query Creator</title>
+  <link rel="shortcut icon" type="image/png" href="../lib/jasmine-2.1.1/jasmine_favicon.png">
+  <link rel="stylesheet" href="../lib/jasmine-2.1.1/jasmine.css">
+  <script src="../lib/require.js"></script>
+  <script src="../lib/jasmine-2.1.1/jasmine.js"></script>
+  <script src="../lib/jasmine-2.1.1/jasmine-html.js"></script>
+  <script src="../lib/jasmine-2.1.1/boot.js"></script>
+  <script>
+    require.config({
+      baseUrl: "../src",
+      paths: {
+        "lib" : "../lib",
+        "spec" : "../spec"
+      }
+    });
+    require([
+    'spec/queryCreatorSpec'
+    ],
+    function () {
+      if (jsApiReporter.finished === true)
+        jasmine.getEnv().execute();
+    });
+    </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dev/js/spec/queryCreatorSpec.js b/dev/js/spec/queryCreatorSpec.js
new file mode 100644
index 0000000..42eac3d
--- /dev/null
+++ b/dev/js/spec/queryCreatorSpec.js
@@ -0,0 +1,237 @@
+function matchInfoFactory () {
+  var info = document.createElement('div');
+  info.className = 'matchinfo';
+  info.innerHTML = 
+    "  <div class=\"matchtable\">" +
+    "    <table>" +
+    "      <thead>" +
+    "        <tr>" +
+    "          <th>Foundry</th>" +
+    "          <th>Layer</th>" +
+    "          <th>Der</th>" +
+    "          <th>älteste</th>" +
+    "          <th>lebende</th>" +
+    "          <th>Baum</th>" +
+    "        </tr>" +
+    "      </thead>" +
+    "      <tbody>" +
+    "        <tr tabindex=\"0\">" +
+    "          <th>corenlp</th>" +
+    "          <th>p</th>" +
+    "          <td>ART</td>" +
+    "          <td>ADJA</td>" +
+    "          <td>ADJA<br>ADJD</td>" +
+    "          <td>NN</td>" +
+    "        </tr>" +
+    "        <tr tabindex=\"0\">" +
+    "          <th>opennlp</th>" +
+    "          <th>p</th>" +
+    "          <td>ART</td>" +
+    "          <td>ADJA</td>" +
+    "          <td></td>" +
+    "          <td>NN</td>" +
+    "        </tr>" +
+    "      </tbody>" +
+    "    </table>" +
+    "  </div>" +
+    "</div>";
+
+  return info;
+};
+
+
+define(['match/querycreator'], function (qcClass) {
+
+  describe('KorAP.QueryCreator', function () {
+
+    it('should be initializable', function () {
+      expect(
+	      function() {
+          qcClass.create()
+        }
+      ).toThrow(new Error("Missing parameters"));
+
+      expect(
+	      function() {
+          qcClass.create("Test")
+        }
+      ).toThrow(new Error("Requires element"));
+
+      expect(
+	      function() {
+          var minfo = document.createElement('div');
+          qcClass.create(minfo);
+        }
+      ).toThrow(new Error("Element contains no match table"));
+
+      expect(qcClass.create(matchInfoFactory()).toString()).toEqual("");
+    });
+
+    it('should listen to click events', function () {
+
+      var matchInfo = matchInfoFactory();
+      var qc = qcClass.create(matchInfo);
+
+      // Nothing to show
+      expect(qc.toString()).toEqual("");
+      expect(qc.shown()).toBe(false);
+      qc.show();
+      expect(qc.shown()).toBe(false);
+      expect(qc.element().className).toEqual("queryfragment");
+
+      // Click on cell 0:0 "Foundry"
+      var cell = matchInfo.querySelector("thead > tr > th:first-child");
+      expect(cell.innerText).toEqual("Foundry");
+      cell.click();
+      expect(cell.classList.contains("chosen")).toBe(false);
+      expect(qc.toString()).toEqual("");
+      expect(qc.shown()).toBe(false);
+
+      // Click on cell 0:2 "Der"
+      cell = matchInfo.querySelector("thead > tr > th:nth-child(3)")
+      expect(cell.innerText).toEqual("Der");
+      cell.click();
+      expect(cell.classList.contains("chosen")).toBeTruthy();
+      expect(qc.toString()).toEqual("[orth=Der]");
+      expect(qc.shown()).toBeTruthy();
+
+      // Click on cell 0:2 "Der" again - to hide
+      cell = matchInfo.querySelector("thead > tr > th:nth-child(3)")
+      expect(cell.innerText).toEqual("Der");
+      cell.click();
+      expect(cell.classList.contains("chosen")).toBe(false);
+      expect(qc.toString()).toEqual("");
+      expect(qc.shown()).toBe(false);
+    });
+
+    it('should create tokens in arbitrary order', function () {
+      var matchInfo = matchInfoFactory();
+      var qc = qcClass.create(matchInfo);
+
+      var cell = matchInfo.querySelector("tbody > tr:nth-child(2) > td:nth-child(4)");
+      expect(cell.innerText).toEqual("ADJA");
+      cell.click();
+      expect(qc.toString()).toEqual("[opennlp/p=ADJA]");
+
+      cell = matchInfo.querySelector("thead > tr > th:nth-child(4)");
+      expect(cell.innerText).toEqual("älteste");
+      cell.click();
+      expect(qc.toString()).toEqual("[opennlp/p=ADJA & orth=älteste]");
+
+      cell = matchInfo.querySelector("tbody > tr > td:nth-child(4)");
+      expect(cell.innerText).toEqual("ADJA");
+      cell.click();
+      expect(qc.toString()).toEqual("[corenlp/p=ADJA & opennlp/p=ADJA & orth=älteste]");
+    });
+
+    it('should create token sequences in arbitrary order', function () {
+      var matchInfo = matchInfoFactory();
+      var qc = qcClass.create(matchInfo);
+
+      var cell = matchInfo.querySelector("thead > tr > th:nth-child(5)");
+      expect(cell.innerText).toEqual("lebende");
+      cell.click();
+      expect(qc.toString()).toEqual("[orth=lebende]");
+
+      cell = matchInfo.querySelector("tbody > tr:nth-child(2) > td:nth-child(3)");
+      expect(cell.innerText).toEqual("ART");
+      cell.click();
+      expect(qc.toString()).toEqual("[opennlp/p=ART][orth=lebende]");
+
+      cell = matchInfo.querySelector("tbody > tr > td:nth-child(4)");
+      expect(cell.innerText).toEqual("ADJA");
+      cell.click();
+      expect(qc.toString()).toEqual("[opennlp/p=ART][corenlp/p=ADJA][orth=lebende]");
+    });
+
+    it('should remove chosen elements again', function () {
+      var matchInfo = matchInfoFactory();
+      var qc = qcClass.create(matchInfo);
+
+      var cell = matchInfo.querySelector("tbody > tr:nth-child(2) > td:nth-child(4)");
+      expect(cell.innerText).toEqual("ADJA");
+      cell.click();
+      expect(qc.toString()).toEqual("[opennlp/p=ADJA]");
+      var cell1 = cell;
+
+      cell = matchInfo.querySelector("thead > tr > th:nth-child(4)");
+      expect(cell.innerText).toEqual("älteste");
+      cell.click();
+      expect(qc.toString()).toEqual("[opennlp/p=ADJA & orth=älteste]");
+      var cell2 = cell;
+
+      cell = matchInfo.querySelector("tbody > tr > td:nth-child(3)");
+      expect(cell.innerText).toEqual("ART");
+      cell.click();
+      expect(qc.toString()).toEqual("[corenlp/p=ART][opennlp/p=ADJA & orth=älteste]");
+      var cell3 = cell;
+
+      cell = matchInfo.querySelector("thead > tr > th:nth-child(6)");
+      expect(cell.innerText).toEqual("Baum");
+      cell.click();
+      expect(qc.toString()).toEqual("[corenlp/p=ART][opennlp/p=ADJA & orth=älteste][orth=Baum]");
+      var cell4 = cell;
+
+      cell = matchInfo.querySelector("thead > tr > th:nth-child(5)");
+      expect(cell.innerText).toEqual("lebende");
+      cell.click();
+      expect(qc.toString()).toEqual("[corenlp/p=ART][opennlp/p=ADJA & orth=älteste][orth=lebende][orth=Baum]");
+      var cell5 = cell;
+
+
+      // Remove annotations again
+      expect(cell5.classList.contains("chosen")).toBeTruthy();
+      cell5.click()
+      expect(cell5.classList.contains("chosen")).toBe(false);
+      expect(qc.toString()).toEqual("[corenlp/p=ART][opennlp/p=ADJA & orth=älteste][orth=Baum]");
+
+      expect(cell2.classList.contains("chosen")).toBeTruthy();
+      cell2.click()
+      expect(cell2.classList.contains("chosen")).toBe(false);
+      expect(qc.toString()).toEqual("[corenlp/p=ART][opennlp/p=ADJA][orth=Baum]");
+
+      expect(cell1.classList.contains("chosen")).toBeTruthy();
+      cell1.click()
+      expect(cell1.classList.contains("chosen")).toBe(false);
+      expect(qc.toString()).toEqual("[corenlp/p=ART][orth=Baum]");
+
+      // Re-add first cell at the same position
+      expect(cell1.classList.contains("chosen")).toBe(false);
+      cell1.click()
+      expect(cell1.classList.contains("chosen")).toBeTruthy();
+      expect(qc.toString()).toEqual("[corenlp/p=ART][opennlp/p=ADJA][orth=Baum]");
+
+      expect(cell3.classList.contains("chosen")).toBeTruthy();
+      cell3.click()
+      expect(cell3.classList.contains("chosen")).toBe(false);
+      expect(qc.toString()).toEqual("[opennlp/p=ADJA][orth=Baum]");
+
+      expect(cell4.classList.contains("chosen")).toBeTruthy();
+      cell4.click()
+      expect(cell4.classList.contains("chosen")).toBe(false);
+      expect(qc.toString()).toEqual("[opennlp/p=ADJA]");
+
+      // Remove last token
+      expect(qc.shown()).toBeTruthy();
+      expect(qc.element().innerHTML).toEqual("<span>New Query:</span><span>[opennlp/p=ADJA]</span>");
+      expect(cell1.classList.contains("chosen")).toBeTruthy();
+      cell1.click()
+      expect(cell1.classList.contains("chosen")).toBe(false);
+      expect(qc.toString()).toEqual("");
+      expect(qc.shown()).toBe(false);
+
+      // Re-add token
+      expect(cell4.classList.contains("chosen")).toBe(false);
+      cell4.click()
+      expect(cell4.classList.contains("chosen")).toBeTruthy();
+      expect(qc.toString()).toEqual("[orth=Baum]");
+      expect(qc.shown()).toBeTruthy();
+      expect(qc.element().innerHTML).toEqual("<span>New Query:</span><span>[orth=Baum]</span>");
+    });
+
+    it('should ignore empty terms');
+    it('should create groups for multiple terms');
+    it('should add whole rows');
+
+  });
+});
diff --git a/dev/js/src/match/querycreator.js b/dev/js/src/match/querycreator.js
index 1da61cc..947e9cd 100644
--- a/dev/js/src/match/querycreator.js
+++ b/dev/js/src/match/querycreator.js
@@ -17,13 +17,25 @@
     // Initialize query creator
     _init : function (matchInfo) {
 
-      // This may be probably a hint helper
+      // Parameter checks
+      if (matchInfo === undefined)
+        throw new Error('Missing parameters');
+      else if (!(matchInfo instanceof Node))
+        throw new Error('Requires element');
+
+      // Collect the token sequence in an array
       this._query = []
+
+      // Remember the matchinfo that is the parent of
+      // the matchtable and the query frafment
       this._matchInfo = matchInfo;
 
       // Get the match table
       this._matchTable = this._matchInfo.getElementsByClassName('matchtable')[0];
 
+      if (this._matchTable === undefined)
+        throw new Error('Element contains no match table');
+
       // Listen on the match table
       this._matchTable.addEventListener(
         "click", this.clickOnAnno.bind(this), false
@@ -189,11 +201,19 @@
       this.show();
     },
 
+    
     // Get element representing annotation line
     element : function () {
       return this._element;
     },
 
+
+    // Check if the query fragment is shown
+    shown : function () {
+      return this._shown;
+    },
+
+    
     // Show annotation fragment
     show : function () {
 
@@ -249,6 +269,9 @@
     // Add query fragment to query bar
     toQueryBar : function (e) {
 
+      if (this._ql === undefined || this._q === undefined)
+        return;
+
       // Set query language field
       var qlf = this._ql.options;
       for (var i in qlf) {