Эх сурвалжийг харах

[core] Fix the autocompleter jasmine tests

This also change the jasmine setup to use require and modularizes the autocompleter tests
Johan Ahlen 10 жил өмнө
parent
commit
ff82088

+ 0 - 925
desktop/core/src/desktop/static/desktop/spec/autocompleteSpec.js

@@ -1,925 +0,0 @@
-// Licensed to Cloudera, Inc. under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  Cloudera, Inc. licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-describe("autocomplete.js", function() {
-
-  var subject;
-
-  var ajaxHelper = {
-    responseForUrls: {}
-  };
-
-  var createOptions = function (type) {
-    return {
-      assistHelper: new AssistHelper({
-        app: "testApp",
-        db: "testDb",
-        user: "testUser"
-      }),
-      snippet: {
-        type: ko.observable(type),
-        getContext: function() { return ko.mapping.fromJS(null) }
-      },
-      notebook: {
-        getContext: function() { return ko.mapping.fromJS(null) }
-      }
-    }
-  };
-
-  beforeAll(function() {
-    $.totalStorage = function(key, value) {
-      return null;
-    };
-
-    jasmine.addMatchers({
-      toEqualAutocompleteValues : function() {
-        return {
-          compare: function(actualItems, expectedValues) {
-            var itemIndex = {};
-
-            if (actualItems.length !== expectedValues.length) {
-              return { pass: false };
-            }
-            $.each(actualItems, function(i, item) {
-              itemIndex[item.value] = true;
-            });
-
-            for (var i = 0; i < expectedValues.length; i++) {
-              if (! itemIndex[expectedValues[i]]) {
-                return { pass: false };
-              }
-            }
-            return { pass: true };
-          }
-        }
-      }
-    });
-
-    spyOn($, "ajax").and.callFake(function(options) {
-      var firstUrlPart = options.url.split("?")[0];
-      expect(ajaxHelper.responseForUrls[firstUrlPart]).toBeDefined("fake response for url " + firstUrlPart + " not found");
-      if (ajaxHelper.responseForUrls[firstUrlPart]) {
-        ajaxHelper.responseForUrls[firstUrlPart].called = true;
-        ajaxHelper.responseForUrls[firstUrlPart].status = 0;
-        options.success(ajaxHelper.responseForUrls[firstUrlPart]);
-      }
-      return({
-        fail: $.noop
-      })
-    });
-  });
-
-  afterEach(function() {
-    $.each(ajaxHelper.responseForUrls, function(key, value) {
-      expect(value.called).toEqual(true, key + " was never called");
-    })
-  });
-
-  beforeEach(function() {
-    subject = new Autocompleter(createOptions("genericSqlType"));
-    ajaxHelper.responseForUrls = {};
-  });
-
-  var createCallbackSpyForValues = function(values, name) {
-    return jasmine.createSpy(name ? name : 'callback', function (value) {
-      expect(value).toEqualAutocompleteValues(values)
-    }).and.callThrough();
-  };
-
-  var assertAutoComplete = function(testDefinition) {
-    ajaxHelper.responseForUrls = testDefinition.serverResponses;
-    var callback = createCallbackSpyForValues(testDefinition.expectedSuggestions);
-    subject.autocomplete(testDefinition.beforeCursor, testDefinition.afterCursor, callback);
-    expect(callback).toHaveBeenCalled();
-  };
-
-  it("should return empty suggestions for empty statement", function() {
-    assertAutoComplete({
-      serverResponses: { },
-      beforeCursor: "",
-      afterCursor: "",
-      expectedSuggestions: []
-    });
-  });
-
-  it("should return empty suggestions for bogus statement", function() {
-    assertAutoComplete({
-      serverResponses: { },
-      beforeCursor: "foo",
-      afterCursor: "bar",
-      expectedSuggestions: []
-    });
-  });
-
-  describe("table completion", function() {
-    it("should suggest table names with no columns", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb" : {
-            tables: ["testTable1", "testTable2"]
-          }
-        },
-        beforeCursor: "SELECT ",
-        afterCursor: "",
-        expectedSuggestions: ["? FROM testTable1", "? FROM testTable2"]
-      });
-    });
-
-    it("should follow keyword case for table name completion", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb" : {
-            tables: ["testTable1", "testTable2"]
-          }
-        },
-        beforeCursor: "select ",
-        afterCursor: "",
-        expectedSuggestions: ["? from testTable1", "? from testTable2"]
-      });
-    });
-
-    it("should suggest table names with *", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb" : {
-            tables: ["testTable1", "testTable2"]
-          }
-        },
-        beforeCursor: "SELECT *",
-        afterCursor: "",
-        expectedSuggestions: [" FROM testTable1", " FROM testTable2"]
-      });
-    });
-
-    it("should suggest table names with started FROM", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb" : {
-            tables: ["testTable1", "testTable2"]
-          }
-        },
-        beforeCursor: "SELECT * fr",
-        afterCursor: "",
-        expectedSuggestions: ["FROM testTable1", "FROM testTable2"]
-      });
-    });
-
-    it("should suggest table names after FROM", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb" : {
-            tables: ["testTable1", "testTable2"]
-          }
-        },
-        beforeCursor: "SELECT * FROM ",
-        afterCursor: "",
-        expectedSuggestions: ["testTable1", "testTable2"]
-      });
-    });
-
-    it("should suggest aliases", function() {
-      assertAutoComplete({
-        serverResponses: {},
-        beforeCursor: "SELECT ",
-        afterCursor: " FROM testTableA   tta, testTableB",
-        expectedSuggestions: ["testTableB.", "tta."]
-      });
-    });
-
-    it("should only suggest table aliases", function() {
-      assertAutoComplete({
-        serverResponses: {},
-        beforeCursor: "SELECT ",
-        afterCursor: " FROM testTableA tta, (SELECT SUM(A*B) total FROM tta.array) ttaSum, testTableB ttb",
-        expectedSuggestions: ["tta.", "ttb."]
-      });
-    });
-
-    // TODO: Fix me...
-    xit("should suggest aliases from nested selects", function() {
-      assertAutoComplete({
-        serverResponses: {},
-        beforeCursor: "SELECT ",
-        afterCursor: " FROM testTableA tta, testTableB ttb, (SELECT SUM(A*B) total FROM tta.array) ttaSum",
-        expectedSuggestions: ["tta.", "ttb.", "ttaSum."]
-      });
-    });
-  });
-
-  describe("hive-specific stuff", function() {
-    beforeEach(function() {
-      subject = new Autocompleter(createOptions("hive"));
-      ajaxHelper.responseForUrls = {};
-    });
-
-    it("should suggest struct from map values", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable/testMap" : {
-            type: "map"
-          },
-          "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
-            fields: [
-              {"type": "string", "name": "fieldA" },
-              {"type": "string", "name": "fieldB" },
-              {"type": "struct",  "name": "fieldC", "fields": [
-                {"type": "string", "name": "fieldC_A" },
-                {"type": "boolean", "name": "fieldC_B"}
-              ]}],
-            type: "struct"
-          }
-        },
-        beforeCursor: "SELECT testMap[\"anyKey\"].",
-        afterCursor: " FROM testTable",
-        expectedSuggestions: ["fieldA", "fieldB", "fieldC"]
-      });
-    });
-
-    it("should suggest struct from map values without a given key", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable/testMap" : {
-            type: "map"
-          },
-          "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
-            fields: [
-              {"type": "string", "name": "fieldA" },
-              {"type": "string", "name": "fieldB" }
-            ],
-            type: "struct"
-          }
-        },
-        beforeCursor: "SELECT testMap[].",
-        afterCursor: " FROM testTable",
-        expectedSuggestions: ["fieldA", "fieldB"]
-      });
-    });
-
-    it("should suggest struct from structs from map values", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable/testMap" : {
-            type: "map"
-          },
-          "/notebook/api/autocomplete/testDb/testTable/testMap/value/fieldC" : {
-            fields: [
-              {"type": "string", "name": "fieldC_A" },
-              {"type": "boolean", "name": "fieldC_B"}
-            ],
-            type: "struct"
-          }
-        },
-        beforeCursor: "SELECT testMap[\"anyKey\"].fieldC.",
-        afterCursor: " FROM testTable",
-        expectedSuggestions: ["fieldC_A", "fieldC_B"]
-      });
-    });
-
-    it("should suggest struct from structs from arrays", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable/testArray" : {
-            type: "array"
-          },
-          "/notebook/api/autocomplete/testDb/testTable/testArray/item/fieldC" : {
-            fields: [
-              {"type": "string", "name": "fieldC_A" },
-              {"type": "boolean", "name": "fieldC_B"}
-            ],
-            type: "struct"
-          }
-        },
-        beforeCursor: "SELECT testArray[1].fieldC.",
-        afterCursor: " FROM testTable",
-        expectedSuggestions: ["fieldC_A", "fieldC_B"]
-      });
-    });
-
-    it("should suggest structs from maps from arrays", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable/testArray" : {
-            type: "array"
-          },
-          "/notebook/api/autocomplete/testDb/testTable/testArray/item/testMap" : {
-            type: "map"
-          },
-          "/notebook/api/autocomplete/testDb/testTable/testArray/item/testMap/value" : {
-            fields: [
-              {"type": "string", "name": "fieldA" },
-              {"type": "boolean", "name": "fieldB"}
-            ],
-            type: "struct"
-          }
-        },
-        beforeCursor: "SELECT testArray[1].testMap[\"key\"].",
-        afterCursor: " FROM testTable",
-        expectedSuggestions: ["fieldA", "fieldB"]
-      });
-    });
-
-    describe("lateral views", function() {
-      it("should suggest structs from exploded item references to arrays", function () {
-        assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testArray/item": {
-              fields: [
-                {"type": "string", "name": "fieldA"},
-                {"type": "string", "name": "fieldB"}
-              ],
-              type: "struct"
-            }
-          },
-          beforeCursor: "SELECT testItem.",
-          afterCursor: " FROM testTable LATERAL VIEW explode(testArray) explodedTable AS testItem",
-          expectedSuggestions: ["fieldA", "fieldB"]
-        });
-      });
-
-      it("should suggest structs from multiple exploded item references to arrays", function () {
-        assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testArrayA/item": {
-              fields: [
-                {"type": "string", "name": "fieldA"},
-                {"type": "string", "name": "fieldB"}
-              ],
-              type: "struct"
-            }
-          },
-          beforeCursor: "SELECT testItemA.",
-          afterCursor: " FROM testTable" +
-                       " LATERAL VIEW explode(testArrayA) explodedTableA AS testItemA" +
-                       " LATERAL VIEW explode(testArrayB) explodedTableB AS testItemB",
-          expectedSuggestions: ["fieldA", "fieldB"]
-        });
-      });
-
-      it("should support table references as arguments of explode function", function() {
-        assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable2/testArrayB/item": {
-              fields: [
-                {"type": "string", "name": "fieldA"},
-                {"type": "string", "name": "fieldB"}
-              ],
-              type: "struct"
-            }
-          },
-          beforeCursor: "SELECT\n testItemA,\n testItemB.",
-          afterCursor: "\n\tFROM\n\t testTable2 tt2\n" +
-          "\t LATERAL VIEW EXPLODE(tt2.testArrayA) explodedTableA AS testItemA\n" +
-          "\t LATERAL VIEW EXPLODE(tt2.testArrayB) explodedTableB AS testItemB",
-          expectedSuggestions: ["fieldA", "fieldB"]
-        });
-      });
-
-      it("should suggest structs from exploded item references to exploded item references to arrays ", function () {
-        assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testArray1/item/testArray2/item": {
-              fields: [
-                {"type": "string", "name": "fieldA"},
-                {"type": "string", "name": "fieldB"}
-              ],
-              type: "struct"
-            }
-          },
-          beforeCursor: "SELECT ta2_exp.",
-          afterCursor: " FROM " +
-          "   testTable tt" +
-          " LATERAL VIEW explode(tt.testArray1) ta1 AS ta1_exp\n" +
-          "   LATERAL VIEW explode(ta1_exp.testArray2)    ta2   AS  ta2_exp",
-          expectedSuggestions: ["fieldA", "fieldB"]
-        });
-      });
-
-      it("should suggest structs from references to exploded arrays", function () {
-        assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testArray/item": {
-              fields: [
-                {"type": "string", "name": "fieldA"},
-                {"type": "string", "name": "fieldB"}
-              ],
-              type: "struct"
-            }
-          },
-          beforeCursor: "SELECT explodedTable.testItem.",
-          afterCursor: " FROM testTable LATERAL VIEW explode(testArray) explodedTable AS testItem",
-          expectedSuggestions: ["fieldA", "fieldB"]
-        });
-      });
-
-      it("should suggest posexploded references to arrays", function () {
-        assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testArray/item": {
-              fields: [
-                {"type": "string", "name": "fieldA"},
-                {"type": "string", "name": "fieldB"}
-              ],
-              type: "struct"
-            }
-          },
-          beforeCursor: "SELECT testValue.",
-          afterCursor: " FROM testTable LATERAL VIEW posexplode(testArray) explodedTable AS (testIndex, testValue)",
-          expectedSuggestions: ["fieldA", "fieldB"]
-        });
-      });
-
-      it("should suggest exploded references to map values", function () {
-        assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testMap/value": {
-              fields: [
-                {"type": "string", "name": "fieldA"},
-                {"type": "string", "name": "fieldB"}
-              ],
-              type: "struct"
-            }
-          },
-          beforeCursor: "SELECT testMapValue.",
-          afterCursor: " FROM testTable LATERAL VIEW explode(testMap) AS (testMapKey, testMapValue)",
-          expectedSuggestions: ["fieldA", "fieldB"]
-        });
-      });
-
-      it("should suggest exploded references to map values from view references", function () {
-        assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testMap/value": {
-              fields: [
-                {"type": "string", "name": "fieldA"},
-                {"type": "string", "name": "fieldB"}
-              ],
-              type: "struct"
-            }
-          },
-          beforeCursor: "SELECT explodedMap.testMapValue.",
-          afterCursor: " FROM testTable LATERAL VIEW explode(testMap) explodedMap AS (testMapKey, testMapValue)",
-          expectedSuggestions: ["fieldA", "fieldB"]
-        });
-      });
-
-      it("should suggest references to exploded references from view reference", function () {
-        assertAutoComplete({
-          serverResponses: {},
-          beforeCursor: "SELECT explodedMap.",
-          afterCursor: " FROM testTable LATERAL VIEW explode(testMap) explodedMap AS (testMapKey, testMapValue)",
-          expectedSuggestions: ["testMapKey", "testMapValue"]
-        });
-      });
-
-      it("should suggest references to exploded references", function () {
-        assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable" : {
-              columns: ["testTableColumn1", "testTableColumn2"]
-            }
-          },
-          beforeCursor: "SELECT ",
-          afterCursor: " FROM testTable LATERAL VIEW explode(testMap) explodedMap AS (testMapKey, testMapValue)",
-          expectedSuggestions: ["*", "explodedMap", "testTableColumn1", "testTableColumn2", "testMapKey", "testMapValue"]
-        });
-      });
-    });
-  });
-
-  describe("impala-specific stuff", function() {
-    beforeEach(function () {
-      subject = new Autocompleter(createOptions("impala"));
-      ajaxHelper.responseForUrls = {};
-    });
-
-    it("should not suggest struct from map values with hive style syntax", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable/testMap[\"anyKey\"]" : {
-            someResponse: true
-          }
-        },
-        beforeCursor: "SELECT testMap[\"anyKey\"].",
-        afterCursor: " FROM testTable",
-        expectedSuggestions: []
-      });
-    });
-
-    it("should suggest fields from nested structs", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable/columnA" : {
-            // Impala has to query every part for it's type, for hive '[' and ']' is used to indicate map or array.
-          },
-          "/notebook/api/autocomplete/testDb/testTable/columnA/fieldC" : {
-            fields: [
-              {"type": "string", "name": "fieldC_A" },
-              {"type": "boolean", "name": "fieldC_B"}
-            ],
-            "type": "struct",
-            "name": "fieldC"
-          }
-        },
-        beforeCursor: "SELECT columnA.fieldC.",
-        afterCursor: " FROM testTable",
-        expectedSuggestions: ["fieldC_A", "fieldC_B"]
-      });
-    });
-
-    it("should suggest fields from map values of type structs", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable/testMap" : {
-            type: "map"
-          },
-          "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
-            fields: [
-              {"type": "string", "name": "fieldA" },
-              {"type": "string", "name": "fieldB" }
-            ],
-            type: "struct"
-          }
-        },
-        beforeCursor: "SELECT tm.",
-        afterCursor: " FROM testTable t, t.testMap tm;",
-        expectedSuggestions: ["*", "key", "fieldA", "fieldB"]
-      });
-    });
-
-    it("should suggest map value if type is scalar", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable/testMap" : {
-            type: "map"
-          },
-          "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
-            type: "int"
-          }
-        },
-        beforeCursor: "SELECT tm.",
-        afterCursor: " FROM testTable t, t.testMap tm;",
-        expectedSuggestions: ["*", "key", "value"]
-      });
-    });
-
-    it("should not suggest items from arrays if complex in select clause", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable/testArray" : {
-            type: "array"
-          },
-          "/notebook/api/autocomplete/testDb/testTable/testArray/item" : {
-            fields: [
-              {"type": "string", "name": "fieldA" },
-              {"type": "string", "name": "fieldB" }
-            ],
-            type: "struct"
-          }
-        },
-        beforeCursor: "SELECT ta.",
-        afterCursor: " FROM testTable t, t.testArray ta;",
-        expectedSuggestions: ["*", "fieldA", "fieldB"]
-      });
-    });
-
-    it("should suggest items from arrays if scalar in select clause", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable/testArray" : {
-            type: "array"
-          },
-          "/notebook/api/autocomplete/testDb/testTable/testArray/item" : {
-            type: "int"
-          }
-        },
-        beforeCursor: "SELECT ta.",
-        afterCursor: " FROM testTable t, t.testArray ta;",
-        expectedSuggestions: ["*", "items"]
-      });
-    });
-
-    it("should suggest items from arrays if complex in from clause", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable/testArray" : {
-            type: "array"
-          },
-          "/notebook/api/autocomplete/testDb/testTable/testArray/item" : {
-            fields: [
-              {"type": "string", "name": "fieldA" },
-              {"type": "string", "name": "fieldB" }
-            ],
-            type: "struct"
-          }
-        },
-        beforeCursor: "SELECT ta.* FROM testTable t, t.testArray ta WHERE ta.",
-        afterCursor: "",
-        expectedSuggestions: ["items", "fieldA", "fieldB"]
-      });
-    });
-
-
-    it("should suggest columns from table refs in from clause", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable" : {
-            columns: ["testTableColumn1", "testTableColumn2"]
-          }
-        },
-        beforeCursor: "SELECT t.*  FROM testTable t, t.",
-        afterCursor: "",
-        expectedSuggestions: ["testTableColumn1", "testTableColumn2"]
-      });
-    });
-
-    it("should suggest map references in select", function() {
-      assertAutoComplete({
-        serverResponses: {},
-        beforeCursor: "SELECT ",
-        afterCursor: " FROM testTable t, t.testMap tm;",
-        expectedSuggestions: ["t.", "tm."]
-      });
-    });
-
-    it("should suggest fields with key and value in where clause from map values of type structs", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable/testMap" : {
-            type: "map"
-          },
-          "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
-            fields: [
-              {"type": "string", "name": "fieldA" },
-              {"type": "string", "name": "fieldB" }
-            ],
-            type: "struct"
-          }
-        },
-        beforeCursor: "SELECT tm.* FROM testTable t, t.testMap tm WHERE tm.",
-        afterCursor: "",
-        expectedSuggestions: ["key", "value", "fieldA", "fieldB"]
-      });
-    });
-
-    it("should suggest fields in where clause from map values of type structs", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable/testMap" : {
-            type: "map"
-          },
-          "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
-            fields: [
-              {"type": "string", "name": "fieldA" },
-              {"type": "string", "name": "fieldB" }
-            ],
-            type: "struct"
-          }
-        },
-        beforeCursor: "SELECT tm.* FROM testTable t, t.testMap tm WHERE tm.value.",
-        afterCursor: "",
-        expectedSuggestions: ["fieldA", "fieldB"]
-      });
-    });
-  });
-
-  describe("field completion", function() {
-
-    it("should suggest columns for table", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable" : {
-            columns: ["testTableColumn1", "testTableColumn2"]
-          }
-        },
-        beforeCursor: "SELECT ",
-        afterCursor: " FROM testTable",
-        expectedSuggestions: ["*", "testTableColumn1", "testTableColumn2"]
-      });
-    });
-
-    it("should suggest columns for table after WHERE", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable" : {
-            columns: ["testTableColumn1", "testTableColumn2"]
-          }
-        },
-        beforeCursor: "SELECT * FROM testTable WHERE ",
-        afterCursor: "",
-        expectedSuggestions: ["testTableColumn1", "testTableColumn2"]
-      });
-    });
-
-    it("should suggest columns for table after ORDER BY ", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable" : {
-            columns: ["testTableColumn1", "testTableColumn2"]
-          }
-        },
-        beforeCursor: "SELECT * FROM testTable ORDER BY ",
-        afterCursor: "",
-        expectedSuggestions: ["testTableColumn1", "testTableColumn2"]
-      });
-    });
-
-    it("should suggest columns for table after ON ", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable1" : {
-            columns: ["testTableColumn1", "testTableColumn2"]
-          }
-        },
-        beforeCursor: "SELECT t1.testTableColumn1, t2.testTableColumn3 FROM testTable1 t1 JOIN testTable2 t2 ON t1.",
-        afterCursor: "",
-        expectedSuggestions: ["testTableColumn1", "testTableColumn2"]
-      });
-    });
-
-    it("should suggest columns for table with table ref", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable" : {
-            columns: ["testTableColumn1", "testTableColumn2"]
-          }
-        },
-        beforeCursor: "SELECT testTable.",
-        afterCursor: " FROM testTable",
-        expectedSuggestions: ["*", "testTableColumn1", "testTableColumn2"]
-      });
-    });
-
-    it("should suggest columns with table alias", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable" : {
-            columns: ["testTableColumn1", "testTableColumn2"]
-          }
-        },
-        beforeCursor: "SELECT tt.",
-        afterCursor: " FROM testTable tt",
-        expectedSuggestions: ["*", "testTableColumn1", "testTableColumn2"]
-      });
-    });
-
-    it("should suggest columns with multiple table aliases", function() {
-      var serverResponses = {
-        "/notebook/api/autocomplete/testDb/testTableA": {
-          columns: ["testTableColumn1", "testTableColumn2"]
-        },
-        "/notebook/api/autocomplete/testDb/testTableB": {
-          columns: ["testTableColumn3", "testTableColumn4"]
-        }
-      };
-      assertAutoComplete({
-        serverResponses: serverResponses,
-        beforeCursor: "SELECT tta.",
-        afterCursor: " FROM testTableA tta, testTableB ttb",
-        expectedSuggestions: ["*", "testTableColumn1", "testTableColumn2"]
-      });
-      assertAutoComplete({
-        serverResponses: serverResponses,
-        beforeCursor: "SELECT ttb.",
-        afterCursor: " FROM testTableA tta, testTableB ttb",
-        expectedSuggestions: ["*", "testTableColumn3", "testTableColumn4"]
-      });
-    });
-
-    describe("struct completion", function() {
-      it("should suggest fields from columns that are structs", function() {
-        assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/columnA" : {
-              fields: [
-                {"type": "string", "name": "fieldA" },
-                {"type": "boolean", "name": "fieldB" },
-                {"type": "struct",  "name": "fieldC", "fields": [
-                  {"type": "string", "name": "fieldC_A" },
-                  {"type": "boolean", "name": "fieldC_B"}
-                ]}
-              ],
-              "type": "struct",
-              "name": "columnB"
-            }
-          },
-          beforeCursor: "SELECT columnA.",
-          afterCursor: " FROM testTable",
-          expectedSuggestions: ["fieldA", "fieldB", "fieldC"]
-        });
-      });
-
-      it("should suggest fields from nested structs", function() {
-        assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/columnA/fieldC" : {
-              fields: [
-                {"type": "string", "name": "fieldC_A" },
-                {"type": "boolean", "name": "fieldC_B"}
-              ],
-              "type": "struct",
-              "name": "fieldC"
-            }
-          },
-          beforeCursor: "SELECT columnA.fieldC.",
-          afterCursor: " FROM testTable",
-          expectedSuggestions: ["fieldC_A", "fieldC_B"]
-        });
-      });
-    });
-  });
-
-  describe("joins", function() {
-
-    it("should suggest tables to join with", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb" : {
-            tables: ["testTable1", "testTable2"]
-          }
-        },
-        beforeCursor: "SELECT * FROM testTable1 JOIN ",
-        afterCursor: "",
-        expectedSuggestions: ["testTable1", "testTable2"]
-      });
-    });
-
-    it("should suggest table references in join condition if not already there", function() {
-      assertAutoComplete({
-        serverResponses: {},
-        beforeCursor: "SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (",
-        afterCursor: "",
-        expectedSuggestions: ["testTable1.", "testTable2."]
-      });
-    });
-
-    it("should suggest table references in join condition if not already there for multiple conditions", function() {
-      assertAutoComplete({
-        serverResponses: {},
-        beforeCursor: "SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable1.testColumn1 = testTable2.testColumn3 AND ",
-        afterCursor: "",
-        expectedSuggestions: ["testTable1.", "testTable2."]
-      });
-    });
-
-    it("should suggest table references in join condition if not already there for multiple conditions", function() {
-      assertAutoComplete({
-        serverResponses: {},
-        beforeCursor: "SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (",
-        afterCursor: " AND testTable1.testColumn1 = testTable2.testColumn3",
-        expectedSuggestions: ["testTable1.", "testTable2."]
-      });
-    });
-
-    it("should suggest field references in join condition if table reference is present", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable2" : {
-            columns: ["testColumn3", "testColumn4"]
-          }
-        },
-        beforeCursor: "SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable2.",
-        afterCursor: "",
-        expectedSuggestions: ["testColumn3", "testColumn4"]
-      });
-    });
-
-    it("should suggest field references in join condition if table reference is present from multiple tables", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable2" : {
-            columns: ["testColumn3", "testColumn4"]
-          }
-        },
-        beforeCursor: "select * from testTable1 JOIN testTable2 on (testTable1.testColumn1 = testTable2.",
-        afterCursor: "",
-        expectedSuggestions: ["testColumn3", "testColumn4"]
-      });
-    });
-
-    it("should suggest field references in join condition if table reference is present from multiple tables for multiple conditions", function() {
-      assertAutoComplete({
-        serverResponses: {
-          "/notebook/api/autocomplete/testDb/testTable1" : {
-            columns: ["testColumn1", "testColumn2"]
-          }
-        },
-        beforeCursor: "SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable1.testColumn1 = testTable2.testColumn3 AND testTable1.",
-        afterCursor: "",
-        expectedSuggestions: ["testColumn1", "testColumn2"]
-      });
-    });
-  })
-});

+ 941 - 0
desktop/core/src/desktop/static/desktop/spec/autocompleterSpec.js

@@ -0,0 +1,941 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+define([
+  'knockout',
+  'desktop/js/autocompleter',
+  'desktop/js/assistHelper'
+], function(ko, Autocompleter, AssistHelper) {
+  describe("autocomplete.js", function() {
+    var subject;
+
+    var ajaxHelper = {
+      responseForUrls: {}
+    };
+
+    var createOptions = function (type) {
+      var notebook = {
+        getContext: function() { return ko.mapping.fromJS(null) }
+      };
+      return {
+        assistHelper: new AssistHelper({
+          notebook: notebook,
+          activeDatabase: "testDb",
+          user: "testUser"
+        }),
+        snippet: {
+          type: ko.observable(type),
+          isSqlDialect: function() { return true; },
+          getContext: function() { return ko.mapping.fromJS(null) }
+        },
+        notebook: notebook
+      }
+    };
+
+    beforeAll(function() {
+      $.totalStorage = function(key, value) {
+        return null;
+      };
+
+      jasmine.addMatchers({
+        toEqualAutocompleteValues : function() {
+          return {
+            compare: function(actualItems, expectedValues) {
+              var itemIndex = {};
+
+              if (actualItems.length !== expectedValues.length) {
+                return { pass: false };
+              }
+              $.each(actualItems, function(i, item) {
+                itemIndex[item.value] = true;
+              });
+
+              for (var i = 0; i < expectedValues.length; i++) {
+                if (! itemIndex[expectedValues[i]]) {
+                  return { pass: false };
+                }
+              }
+              return { pass: true };
+            }
+          }
+        }
+      });
+
+      spyOn($, "ajax").and.callFake(function(options) {
+        var firstUrlPart = options.url.split("?")[0];
+        var response;
+        if (firstUrlPart == "/notebook/api/autocomplete/") {
+          response = {
+            databases: ["testDb"]
+          };
+        } else {
+          expect(ajaxHelper.responseForUrls[firstUrlPart]).toBeDefined("fake response for url " + firstUrlPart + " not found");
+          response = ajaxHelper.responseForUrls[firstUrlPart];
+        }
+        response.called = true;
+        response.status = 0;
+        options.success(response);
+        return({
+          fail: function() {
+            return {
+              always: $.noop
+            }
+          }
+        })
+      });
+    });
+
+    afterEach(function() {
+      $.each(ajaxHelper.responseForUrls, function(key, value) {
+        expect(value.called).toEqual(true, key + " was never called");
+      })
+    });
+
+    beforeEach(function() {
+      subject = new Autocompleter(createOptions("genericSqlType"));
+      ajaxHelper.responseForUrls = {};
+    });
+
+    var createCallbackSpyForValues = function(values, name) {
+      return jasmine.createSpy(name ? name : 'callback', function (value) {
+        expect(value).toEqualAutocompleteValues(values)
+      }).and.callThrough();
+    };
+
+    var assertAutoComplete = function(testDefinition) {
+      ajaxHelper.responseForUrls = testDefinition.serverResponses;
+      var callback = createCallbackSpyForValues(testDefinition.expectedSuggestions);
+      subject.autocomplete(testDefinition.beforeCursor, testDefinition.afterCursor, callback);
+      expect(callback).toHaveBeenCalled();
+    };
+
+    it("should return empty suggestions for empty statement", function() {
+      assertAutoComplete({
+        serverResponses: { },
+        beforeCursor: "",
+        afterCursor: "",
+        expectedSuggestions: []
+      });
+    });
+
+    it("should return empty suggestions for bogus statement", function() {
+      assertAutoComplete({
+        serverResponses: { },
+        beforeCursor: "foo",
+        afterCursor: "bar",
+        expectedSuggestions: []
+      });
+    });
+
+    describe("table completion", function() {
+      it("should suggest table names with no columns", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb" : {
+              tables: ["testTable1", "testTable2"]
+            }
+          },
+          beforeCursor: "SELECT ",
+          afterCursor: "",
+          expectedSuggestions: ["? FROM testTable1", "? FROM testTable2"]
+        });
+      });
+
+      it("should follow keyword case for table name completion", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb" : {
+              tables: ["testTable1", "testTable2"]
+            }
+          },
+          beforeCursor: "select ",
+          afterCursor: "",
+          expectedSuggestions: ["? from testTable1", "? from testTable2"]
+        });
+      });
+
+      it("should suggest table names with *", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb" : {
+              tables: ["testTable1", "testTable2"]
+            }
+          },
+          beforeCursor: "SELECT *",
+          afterCursor: "",
+          expectedSuggestions: [" FROM testTable1", " FROM testTable2"]
+        });
+      });
+
+      it("should suggest table names with started FROM", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb" : {
+              tables: ["testTable1", "testTable2"]
+            }
+          },
+          beforeCursor: "SELECT * fr",
+          afterCursor: "",
+          expectedSuggestions: ["FROM testTable1", "FROM testTable2"]
+        });
+      });
+
+      it("should suggest table names after FROM", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb" : {
+              tables: ["testTable1", "testTable2"]
+            }
+          },
+          beforeCursor: "SELECT * FROM ",
+          afterCursor: "",
+          expectedSuggestions: ["testTable1", "testTable2"]
+        });
+      });
+
+      it("should suggest aliases", function() {
+        assertAutoComplete({
+          serverResponses: {},
+          beforeCursor: "SELECT ",
+          afterCursor: " FROM testTableA   tta, testTableB",
+          expectedSuggestions: ["testTableB.", "tta."]
+        });
+      });
+
+      it("should only suggest table aliases", function() {
+        assertAutoComplete({
+          serverResponses: {},
+          beforeCursor: "SELECT ",
+          afterCursor: " FROM testTableA tta, (SELECT SUM(A*B) total FROM tta.array) ttaSum, testTableB ttb",
+          expectedSuggestions: ["tta.", "ttb."]
+        });
+      });
+
+      // TODO: Fix me...
+      xit("should suggest aliases from nested selects", function() {
+        assertAutoComplete({
+          serverResponses: {},
+          beforeCursor: "SELECT ",
+          afterCursor: " FROM testTableA tta, testTableB ttb, (SELECT SUM(A*B) total FROM tta.array) ttaSum",
+          expectedSuggestions: ["tta.", "ttb.", "ttaSum."]
+        });
+      });
+    });
+
+    describe("hive-specific stuff", function() {
+      beforeEach(function() {
+        subject = new Autocompleter(createOptions("hive"));
+        ajaxHelper.responseForUrls = {};
+      });
+
+      it("should suggest struct from map values", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable/testMap" : {
+              type: "map"
+            },
+            "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
+              fields: [
+                {"type": "string", "name": "fieldA" },
+                {"type": "string", "name": "fieldB" },
+                {"type": "struct",  "name": "fieldC", "fields": [
+                  {"type": "string", "name": "fieldC_A" },
+                  {"type": "boolean", "name": "fieldC_B"}
+                ]}],
+              type: "struct"
+            }
+          },
+          beforeCursor: "SELECT testMap[\"anyKey\"].",
+          afterCursor: " FROM testTable",
+          expectedSuggestions: ["fieldA", "fieldB", "fieldC"]
+        });
+      });
+
+      it("should suggest struct from map values without a given key", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable/testMap" : {
+              type: "map"
+            },
+            "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
+              fields: [
+                {"type": "string", "name": "fieldA" },
+                {"type": "string", "name": "fieldB" }
+              ],
+              type: "struct"
+            }
+          },
+          beforeCursor: "SELECT testMap[].",
+          afterCursor: " FROM testTable",
+          expectedSuggestions: ["fieldA", "fieldB"]
+        });
+      });
+
+      it("should suggest struct from structs from map values", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable/testMap" : {
+              type: "map"
+            },
+            "/notebook/api/autocomplete/testDb/testTable/testMap/value/fieldC" : {
+              fields: [
+                {"type": "string", "name": "fieldC_A" },
+                {"type": "boolean", "name": "fieldC_B"}
+              ],
+              type: "struct"
+            }
+          },
+          beforeCursor: "SELECT testMap[\"anyKey\"].fieldC.",
+          afterCursor: " FROM testTable",
+          expectedSuggestions: ["fieldC_A", "fieldC_B"]
+        });
+      });
+
+      it("should suggest struct from structs from arrays", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable/testArray" : {
+              type: "array"
+            },
+            "/notebook/api/autocomplete/testDb/testTable/testArray/item/fieldC" : {
+              fields: [
+                {"type": "string", "name": "fieldC_A" },
+                {"type": "boolean", "name": "fieldC_B"}
+              ],
+              type: "struct"
+            }
+          },
+          beforeCursor: "SELECT testArray[1].fieldC.",
+          afterCursor: " FROM testTable",
+          expectedSuggestions: ["fieldC_A", "fieldC_B"]
+        });
+      });
+
+      it("should suggest structs from maps from arrays", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable/testArray" : {
+              type: "array"
+            },
+            "/notebook/api/autocomplete/testDb/testTable/testArray/item/testMap" : {
+              type: "map"
+            },
+            "/notebook/api/autocomplete/testDb/testTable/testArray/item/testMap/value" : {
+              fields: [
+                {"type": "string", "name": "fieldA" },
+                {"type": "boolean", "name": "fieldB"}
+              ],
+              type: "struct"
+            }
+          },
+          beforeCursor: "SELECT testArray[1].testMap[\"key\"].",
+          afterCursor: " FROM testTable",
+          expectedSuggestions: ["fieldA", "fieldB"]
+        });
+      });
+
+      describe("lateral views", function() {
+        it("should suggest structs from exploded item references to arrays", function () {
+          assertAutoComplete({
+            serverResponses: {
+              "/notebook/api/autocomplete/testDb/testTable/testArray/item": {
+                fields: [
+                  {"type": "string", "name": "fieldA"},
+                  {"type": "string", "name": "fieldB"}
+                ],
+                type: "struct"
+              }
+            },
+            beforeCursor: "SELECT testItem.",
+            afterCursor: " FROM testTable LATERAL VIEW explode(testArray) explodedTable AS testItem",
+            expectedSuggestions: ["fieldA", "fieldB"]
+          });
+        });
+
+        it("should suggest structs from multiple exploded item references to arrays", function () {
+          assertAutoComplete({
+            serverResponses: {
+              "/notebook/api/autocomplete/testDb/testTable/testArrayA/item": {
+                fields: [
+                  {"type": "string", "name": "fieldA"},
+                  {"type": "string", "name": "fieldB"}
+                ],
+                type: "struct"
+              }
+            },
+            beforeCursor: "SELECT testItemA.",
+            afterCursor: " FROM testTable" +
+                         " LATERAL VIEW explode(testArrayA) explodedTableA AS testItemA" +
+                         " LATERAL VIEW explode(testArrayB) explodedTableB AS testItemB",
+            expectedSuggestions: ["fieldA", "fieldB"]
+          });
+        });
+
+        it("should support table references as arguments of explode function", function() {
+          assertAutoComplete({
+            serverResponses: {
+              "/notebook/api/autocomplete/testDb/testTable2/testArrayB/item": {
+                fields: [
+                  {"type": "string", "name": "fieldA"},
+                  {"type": "string", "name": "fieldB"}
+                ],
+                type: "struct"
+              }
+            },
+            beforeCursor: "SELECT\n testItemA,\n testItemB.",
+            afterCursor: "\n\tFROM\n\t testTable2 tt2\n" +
+            "\t LATERAL VIEW EXPLODE(tt2.testArrayA) explodedTableA AS testItemA\n" +
+            "\t LATERAL VIEW EXPLODE(tt2.testArrayB) explodedTableB AS testItemB",
+            expectedSuggestions: ["fieldA", "fieldB"]
+          });
+        });
+
+        it("should suggest structs from exploded item references to exploded item references to arrays ", function () {
+          assertAutoComplete({
+            serverResponses: {
+              "/notebook/api/autocomplete/testDb/testTable/testArray1/item/testArray2/item": {
+                fields: [
+                  {"type": "string", "name": "fieldA"},
+                  {"type": "string", "name": "fieldB"}
+                ],
+                type: "struct"
+              }
+            },
+            beforeCursor: "SELECT ta2_exp.",
+            afterCursor: " FROM " +
+            "   testTable tt" +
+            " LATERAL VIEW explode(tt.testArray1) ta1 AS ta1_exp\n" +
+            "   LATERAL VIEW explode(ta1_exp.testArray2)    ta2   AS  ta2_exp",
+            expectedSuggestions: ["fieldA", "fieldB"]
+          });
+        });
+
+        it("should suggest structs from references to exploded arrays", function () {
+          assertAutoComplete({
+            serverResponses: {
+              "/notebook/api/autocomplete/testDb/testTable/testArray/item": {
+                fields: [
+                  {"type": "string", "name": "fieldA"},
+                  {"type": "string", "name": "fieldB"}
+                ],
+                type: "struct"
+              }
+            },
+            beforeCursor: "SELECT explodedTable.testItem.",
+            afterCursor: " FROM testTable LATERAL VIEW explode(testArray) explodedTable AS testItem",
+            expectedSuggestions: ["fieldA", "fieldB"]
+          });
+        });
+
+        it("should suggest posexploded references to arrays", function () {
+          assertAutoComplete({
+            serverResponses: {
+              "/notebook/api/autocomplete/testDb/testTable/testArray/item": {
+                fields: [
+                  {"type": "string", "name": "fieldA"},
+                  {"type": "string", "name": "fieldB"}
+                ],
+                type: "struct"
+              }
+            },
+            beforeCursor: "SELECT testValue.",
+            afterCursor: " FROM testTable LATERAL VIEW posexplode(testArray) explodedTable AS (testIndex, testValue)",
+            expectedSuggestions: ["fieldA", "fieldB"]
+          });
+        });
+
+        it("should suggest exploded references to map values", function () {
+          assertAutoComplete({
+            serverResponses: {
+              "/notebook/api/autocomplete/testDb/testTable/testMap/value": {
+                fields: [
+                  {"type": "string", "name": "fieldA"},
+                  {"type": "string", "name": "fieldB"}
+                ],
+                type: "struct"
+              }
+            },
+            beforeCursor: "SELECT testMapValue.",
+            afterCursor: " FROM testTable LATERAL VIEW explode(testMap) AS (testMapKey, testMapValue)",
+            expectedSuggestions: ["fieldA", "fieldB"]
+          });
+        });
+
+        it("should suggest exploded references to map values from view references", function () {
+          assertAutoComplete({
+            serverResponses: {
+              "/notebook/api/autocomplete/testDb/testTable/testMap/value": {
+                fields: [
+                  {"type": "string", "name": "fieldA"},
+                  {"type": "string", "name": "fieldB"}
+                ],
+                type: "struct"
+              }
+            },
+            beforeCursor: "SELECT explodedMap.testMapValue.",
+            afterCursor: " FROM testTable LATERAL VIEW explode(testMap) explodedMap AS (testMapKey, testMapValue)",
+            expectedSuggestions: ["fieldA", "fieldB"]
+          });
+        });
+
+        it("should suggest references to exploded references from view reference", function () {
+          assertAutoComplete({
+            serverResponses: {},
+            beforeCursor: "SELECT explodedMap.",
+            afterCursor: " FROM testTable LATERAL VIEW explode(testMap) explodedMap AS (testMapKey, testMapValue)",
+            expectedSuggestions: ["testMapKey", "testMapValue"]
+          });
+        });
+
+        it("should suggest references to exploded references", function () {
+          assertAutoComplete({
+            serverResponses: {
+              "/notebook/api/autocomplete/testDb/testTable" : {
+                columns: ["testTableColumn1", "testTableColumn2"]
+              }
+            },
+            beforeCursor: "SELECT ",
+            afterCursor: " FROM testTable LATERAL VIEW explode(testMap) explodedMap AS (testMapKey, testMapValue)",
+            expectedSuggestions: ["*", "explodedMap", "testTableColumn1", "testTableColumn2", "testMapKey", "testMapValue"]
+          });
+        });
+      });
+    });
+
+    describe("impala-specific stuff", function() {
+      beforeEach(function () {
+        subject = new Autocompleter(createOptions("impala"));
+        ajaxHelper.responseForUrls = {};
+      });
+
+      it("should not suggest struct from map values with hive style syntax", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable/testMap[\"anyKey\"]" : {
+              someResponse: true
+            }
+          },
+          beforeCursor: "SELECT testMap[\"anyKey\"].",
+          afterCursor: " FROM testTable",
+          expectedSuggestions: []
+        });
+      });
+
+      it("should suggest fields from nested structs", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable/columnA" : {
+              // Impala has to query every part for it's type, for hive '[' and ']' is used to indicate map or array.
+            },
+            "/notebook/api/autocomplete/testDb/testTable/columnA/fieldC" : {
+              fields: [
+                {"type": "string", "name": "fieldC_A" },
+                {"type": "boolean", "name": "fieldC_B"}
+              ],
+              "type": "struct",
+              "name": "fieldC"
+            }
+          },
+          beforeCursor: "SELECT columnA.fieldC.",
+          afterCursor: " FROM testTable",
+          expectedSuggestions: ["fieldC_A", "fieldC_B"]
+        });
+      });
+
+      it("should suggest fields from map values of type structs", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable/testMap" : {
+              type: "map"
+            },
+            "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
+              fields: [
+                {"type": "string", "name": "fieldA" },
+                {"type": "string", "name": "fieldB" }
+              ],
+              type: "struct"
+            }
+          },
+          beforeCursor: "SELECT tm.",
+          afterCursor: " FROM testTable t, t.testMap tm;",
+          expectedSuggestions: ["*", "key", "fieldA", "fieldB"]
+        });
+      });
+
+      it("should suggest map value if type is scalar", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable/testMap" : {
+              type: "map"
+            },
+            "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
+              type: "int"
+            }
+          },
+          beforeCursor: "SELECT tm.",
+          afterCursor: " FROM testTable t, t.testMap tm;",
+          expectedSuggestions: ["*", "key", "value"]
+        });
+      });
+
+      it("should not suggest items from arrays if complex in select clause", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable/testArray" : {
+              type: "array"
+            },
+            "/notebook/api/autocomplete/testDb/testTable/testArray/item" : {
+              fields: [
+                {"type": "string", "name": "fieldA" },
+                {"type": "string", "name": "fieldB" }
+              ],
+              type: "struct"
+            }
+          },
+          beforeCursor: "SELECT ta.",
+          afterCursor: " FROM testTable t, t.testArray ta;",
+          expectedSuggestions: ["*", "fieldA", "fieldB"]
+        });
+      });
+
+      it("should suggest items from arrays if scalar in select clause", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable/testArray" : {
+              type: "array"
+            },
+            "/notebook/api/autocomplete/testDb/testTable/testArray/item" : {
+              type: "int"
+            }
+          },
+          beforeCursor: "SELECT ta.",
+          afterCursor: " FROM testTable t, t.testArray ta;",
+          expectedSuggestions: ["*", "items"]
+        });
+      });
+
+      it("should suggest items from arrays if complex in from clause", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable/testArray" : {
+              type: "array"
+            },
+            "/notebook/api/autocomplete/testDb/testTable/testArray/item" : {
+              fields: [
+                {"type": "string", "name": "fieldA" },
+                {"type": "string", "name": "fieldB" }
+              ],
+              type: "struct"
+            }
+          },
+          beforeCursor: "SELECT ta.* FROM testTable t, t.testArray ta WHERE ta.",
+          afterCursor: "",
+          expectedSuggestions: ["items", "fieldA", "fieldB"]
+        });
+      });
+
+
+      it("should suggest columns from table refs in from clause", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable" : {
+              columns: ["testTableColumn1", "testTableColumn2"]
+            }
+          },
+          beforeCursor: "SELECT t.*  FROM testTable t, t.",
+          afterCursor: "",
+          expectedSuggestions: ["testTableColumn1", "testTableColumn2"]
+        });
+      });
+
+      it("should suggest map references in select", function() {
+        assertAutoComplete({
+          serverResponses: {},
+          beforeCursor: "SELECT ",
+          afterCursor: " FROM testTable t, t.testMap tm;",
+          expectedSuggestions: ["t.", "tm."]
+        });
+      });
+
+      it("should suggest fields with key and value in where clause from map values of type structs", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable/testMap" : {
+              type: "map"
+            },
+            "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
+              fields: [
+                {"type": "string", "name": "fieldA" },
+                {"type": "string", "name": "fieldB" }
+              ],
+              type: "struct"
+            }
+          },
+          beforeCursor: "SELECT tm.* FROM testTable t, t.testMap tm WHERE tm.",
+          afterCursor: "",
+          expectedSuggestions: ["key", "value", "fieldA", "fieldB"]
+        });
+      });
+
+      it("should suggest fields in where clause from map values of type structs", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable/testMap" : {
+              type: "map"
+            },
+            "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
+              fields: [
+                {"type": "string", "name": "fieldA" },
+                {"type": "string", "name": "fieldB" }
+              ],
+              type: "struct"
+            }
+          },
+          beforeCursor: "SELECT tm.* FROM testTable t, t.testMap tm WHERE tm.value.",
+          afterCursor: "",
+          expectedSuggestions: ["fieldA", "fieldB"]
+        });
+      });
+    });
+
+    describe("field completion", function() {
+
+      it("should suggest columns for table", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable" : {
+              columns: ["testTableColumn1", "testTableColumn2"]
+            }
+          },
+          beforeCursor: "SELECT ",
+          afterCursor: " FROM testTable",
+          expectedSuggestions: ["*", "testTableColumn1", "testTableColumn2"]
+        });
+      });
+
+      it("should suggest columns for table after WHERE", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable" : {
+              columns: ["testTableColumn1", "testTableColumn2"]
+            }
+          },
+          beforeCursor: "SELECT * FROM testTable WHERE ",
+          afterCursor: "",
+          expectedSuggestions: ["testTableColumn1", "testTableColumn2"]
+        });
+      });
+
+      it("should suggest columns for table after ORDER BY ", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable" : {
+              columns: ["testTableColumn1", "testTableColumn2"]
+            }
+          },
+          beforeCursor: "SELECT * FROM testTable ORDER BY ",
+          afterCursor: "",
+          expectedSuggestions: ["testTableColumn1", "testTableColumn2"]
+        });
+      });
+
+      it("should suggest columns for table after ON ", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable1" : {
+              columns: ["testTableColumn1", "testTableColumn2"]
+            }
+          },
+          beforeCursor: "SELECT t1.testTableColumn1, t2.testTableColumn3 FROM testTable1 t1 JOIN testTable2 t2 ON t1.",
+          afterCursor: "",
+          expectedSuggestions: ["testTableColumn1", "testTableColumn2"]
+        });
+      });
+
+      it("should suggest columns for table with table ref", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable" : {
+              columns: ["testTableColumn1", "testTableColumn2"]
+            }
+          },
+          beforeCursor: "SELECT testTable.",
+          afterCursor: " FROM testTable",
+          expectedSuggestions: ["*", "testTableColumn1", "testTableColumn2"]
+        });
+      });
+
+      it("should suggest columns with table alias", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable" : {
+              columns: ["testTableColumn1", "testTableColumn2"]
+            }
+          },
+          beforeCursor: "SELECT tt.",
+          afterCursor: " FROM testTable tt",
+          expectedSuggestions: ["*", "testTableColumn1", "testTableColumn2"]
+        });
+      });
+
+      it("should suggest columns with multiple table aliases", function() {
+        var serverResponses = {
+          "/notebook/api/autocomplete/testDb/testTableA": {
+            columns: ["testTableColumn1", "testTableColumn2"]
+          },
+          "/notebook/api/autocomplete/testDb/testTableB": {
+            columns: ["testTableColumn3", "testTableColumn4"]
+          }
+        };
+        assertAutoComplete({
+          serverResponses: serverResponses,
+          beforeCursor: "SELECT tta.",
+          afterCursor: " FROM testTableA tta, testTableB ttb",
+          expectedSuggestions: ["*", "testTableColumn1", "testTableColumn2"]
+        });
+        assertAutoComplete({
+          serverResponses: serverResponses,
+          beforeCursor: "SELECT ttb.",
+          afterCursor: " FROM testTableA tta, testTableB ttb",
+          expectedSuggestions: ["*", "testTableColumn3", "testTableColumn4"]
+        });
+      });
+
+      describe("struct completion", function() {
+        it("should suggest fields from columns that are structs", function() {
+          assertAutoComplete({
+            serverResponses: {
+              "/notebook/api/autocomplete/testDb/testTable/columnA" : {
+                fields: [
+                  {"type": "string", "name": "fieldA" },
+                  {"type": "boolean", "name": "fieldB" },
+                  {"type": "struct",  "name": "fieldC", "fields": [
+                    {"type": "string", "name": "fieldC_A" },
+                    {"type": "boolean", "name": "fieldC_B"}
+                  ]}
+                ],
+                "type": "struct",
+                "name": "columnB"
+              }
+            },
+            beforeCursor: "SELECT columnA.",
+            afterCursor: " FROM testTable",
+            expectedSuggestions: ["fieldA", "fieldB", "fieldC"]
+          });
+        });
+
+        it("should suggest fields from nested structs", function() {
+          assertAutoComplete({
+            serverResponses: {
+              "/notebook/api/autocomplete/testDb/testTable/columnA/fieldC" : {
+                fields: [
+                  {"type": "string", "name": "fieldC_A" },
+                  {"type": "boolean", "name": "fieldC_B"}
+                ],
+                "type": "struct",
+                "name": "fieldC"
+              }
+            },
+            beforeCursor: "SELECT columnA.fieldC.",
+            afterCursor: " FROM testTable",
+            expectedSuggestions: ["fieldC_A", "fieldC_B"]
+          });
+        });
+      });
+    });
+
+    describe("joins", function() {
+
+      it("should suggest tables to join with", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb" : {
+              tables: ["testTable1", "testTable2"]
+            }
+          },
+          beforeCursor: "SELECT * FROM testTable1 JOIN ",
+          afterCursor: "",
+          expectedSuggestions: ["testTable1", "testTable2"]
+        });
+      });
+
+      it("should suggest table references in join condition if not already there", function() {
+        assertAutoComplete({
+          serverResponses: {},
+          beforeCursor: "SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (",
+          afterCursor: "",
+          expectedSuggestions: ["testTable1.", "testTable2."]
+        });
+      });
+
+      it("should suggest table references in join condition if not already there for multiple conditions", function() {
+        assertAutoComplete({
+          serverResponses: {},
+          beforeCursor: "SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable1.testColumn1 = testTable2.testColumn3 AND ",
+          afterCursor: "",
+          expectedSuggestions: ["testTable1.", "testTable2."]
+        });
+      });
+
+      it("should suggest table references in join condition if not already there for multiple conditions", function() {
+        assertAutoComplete({
+          serverResponses: {},
+          beforeCursor: "SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (",
+          afterCursor: " AND testTable1.testColumn1 = testTable2.testColumn3",
+          expectedSuggestions: ["testTable1.", "testTable2."]
+        });
+      });
+
+      it("should suggest field references in join condition if table reference is present", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable2" : {
+              columns: ["testColumn3", "testColumn4"]
+            }
+          },
+          beforeCursor: "SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable2.",
+          afterCursor: "",
+          expectedSuggestions: ["testColumn3", "testColumn4"]
+        });
+      });
+
+      it("should suggest field references in join condition if table reference is present from multiple tables", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable2" : {
+              columns: ["testColumn3", "testColumn4"]
+            }
+          },
+          beforeCursor: "select * from testTable1 JOIN testTable2 on (testTable1.testColumn1 = testTable2.",
+          afterCursor: "",
+          expectedSuggestions: ["testColumn3", "testColumn4"]
+        });
+      });
+
+      it("should suggest field references in join condition if table reference is present from multiple tables for multiple conditions", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/testDb/testTable1" : {
+              columns: ["testColumn1", "testColumn2"]
+            }
+          },
+          beforeCursor: "SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable1.testColumn1 = testTable2.testColumn3 AND testTable1.",
+          afterCursor: "",
+          expectedSuggestions: ["testColumn1", "testColumn2"]
+        });
+      });
+    })
+  });
+});

+ 23 - 12
desktop/core/src/desktop/templates/common_jasmine.mako

@@ -14,6 +14,8 @@
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
 
+<%namespace name="require" file="/require.mako" />
+
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
         "http://www.w3.org/TR/html4/loose.dtd">
 <html>
@@ -23,23 +25,32 @@
     <link rel="shortcut icon" type="image/png" href="${ static('desktop/ext/js/jasmine-2.3.4/jasmine_favicon.png') }">
     <link rel="stylesheet" href="${ static('desktop/ext/js/jasmine-2.3.4/jasmine.css') }">
 
-    <script src="${ static('desktop/ext/js/jasmine-2.3.4/jasmine.js') }"></script>
-    <script src="${ static('desktop/ext/js/jasmine-2.3.4/jasmine-html.js') }"></script>
-    <script src="${ static('desktop/ext/js/jasmine-2.3.4/boot.js') }"></script>
-
-
     <script type="text/javascript" src="${ static('desktop/ext/js/jquery/jquery-2.1.1.min.js') }"></script>
     <script type="text/javascript" src="${ static('desktop/js/jquery.migration.js') }"></script>
-    <script type="text/javascript" src="${ static('desktop/ext/js/knockout.min.js') }"></script>
 
-    <%block name="specs"/>
-
-    <script type="text/javascript">
-        (function() {
-
-        })();
+    ${ require.config() }
+
+    <script type="text/javascript" charset="utf-8">
+      // Adds the jasmine dependencies to the existing require config.
+      require.config({
+        baseUrl: "${ static('') }",
+        paths: {
+          'jasmine': 'desktop/ext/js/jasmine-2.3.4/jasmine',
+          'jasmine-html': 'desktop/ext/js/jasmine-2.3.4/jasmine-html',
+          'jasmine-boot': 'desktop/ext/js/jasmine-2.3.4/boot'
+        },
+        shim: {
+          'jasmine-html': {
+            deps : ['jasmine']
+          },
+          'jasmine-boot': {
+            deps : ['jasmine', 'jasmine-html']
+          }
+        }
+      })
     </script>
 
+    <%block name="specs"/>
 </head>
 
 <body>

+ 10 - 13
desktop/core/src/desktop/templates/jasmine.mako

@@ -21,18 +21,15 @@
 <%inherit file="common_jasmine.mako"/>
 
 <%block name="specs">
-  <link href="${ static('desktop/css/hue3.css') }" rel="stylesheet">
-
-  <script src="${ static('desktop/ext/js/jquery/plugins/jquery.dataTables.1.8.2.min.js') }"></script>
-  <script src="${ static('desktop/ext/js/bootstrap.min.js') }"></script>
-  <script src="${ static('desktop/ext/js/knockout.min.js') }"></script>
-  <script src="${ static('desktop/ext/js/knockout-mapping.min.js') }"></script>
-
-  <script src="${ static('desktop/js/hue.utils.js') }"></script>
-
-  ## Specs below
-  <script src="${ static('desktop/js/assistHelper.js') }"></script>
-  <script src="${ static('desktop/js/autocomplete.js') }"></script>
-  <script src="${ static('desktop/spec/autocompleteSpec.js') }"></script>
+  <script type="text/javascript" charset="utf-8">
+  require(['jasmine-boot'], function () {
+    // Add specs below
+    require([
+      'desktop/spec/autocompleterSpec'
+    ], function() {
+      window.onload();
+    });
+  });
+  </script>
 </%block>