1
0
mirror of https://github.com/adobe/brackets.git synced 2024-11-20 18:02:54 +01:00
brackets/test/spec/CodeHint-test.js

499 lines
20 KiB
JavaScript

/*
* Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/
/*global describe, beforeEach, afterEach, it, runs, waitsForDone, expect, beforeFirst, afterLast */
define(function (require, exports, module) {
"use strict";
// Load dependent modules
var SpecRunnerUtils = require("spec/SpecRunnerUtils"),
KeyEvent = require("utils/KeyEvent"),
Commands = require("command/Commands"),
EditorManager, // loaded from brackets.test
CommandManager,
CodeHintManager,
KeyBindingManager;
var testPath = SpecRunnerUtils.getTestPath("/spec/CodeHint-test-files"),
testWindow;
describe("CodeHintManager", function () {
this.category = "integration";
/**
* Performs setup for a code hint test. Opens a file and set pos.
*
* @param {!string} openFile Project relative file path to open in a main editor.
* @param {!number|Array} openPos The pos within openFile to place the IP, or an array
* representing a multiple selection to set.
*/
function initCodeHintTest(openFile, openPos) {
SpecRunnerUtils.loadProjectInTestWindow(testPath);
runs(function () {
var promise = SpecRunnerUtils.openProjectFiles([openFile]);
waitsForDone(promise);
});
runs(function () {
var editor = EditorManager.getCurrentFullEditor();
if (Array.isArray(openPos)) {
editor.setSelections(openPos);
} else {
editor.setCursorPos(openPos.line, openPos.ch);
}
});
}
beforeFirst(function () {
SpecRunnerUtils.createTestWindowAndRun(this, function (w) {
testWindow = w;
// uncomment this line to debug test window:
//testWindow.brackets.app.showDeveloperTools();
// Load module instances from brackets.test
CodeHintManager = testWindow.brackets.test.CodeHintManager;
EditorManager = testWindow.brackets.test.EditorManager;
CommandManager = testWindow.brackets.test.CommandManager;
KeyBindingManager = testWindow.brackets.test.KeyBindingManager;
});
});
afterLast(function () {
testWindow = null;
CodeHintManager = null;
EditorManager = null;
CommandManager = null;
KeyBindingManager = null;
SpecRunnerUtils.closeTestWindow();
});
afterEach(function () {
runs(function () {
testWindow.closeAllFiles();
});
});
function invokeCodeHints() {
CommandManager.execute(Commands.SHOW_CODE_HINTS);
}
// Note: these don't request hint results - they only examine hints that might already be open
function expectNoHints() {
var codeHintList = CodeHintManager._getCodeHintList();
expect(codeHintList).toBeFalsy();
}
function expectSomeHints() {
var codeHintList = CodeHintManager._getCodeHintList();
expect(codeHintList).toBeTruthy();
expect(codeHintList.isOpen()).toBe(true);
return codeHintList;
}
// TODO: There seems to be an issue with CodeHintManager._removeHintProvider because of which the tests
// added for "Hint Provider Registration" are interfering with this test case
// (mock provider added for new language shows up for RegExp, even though no code hint should show up)
// I am unable to reproduce this in a real scenario though/
// This is the reason this test case is being added before the registration test cases.
// We need to either figure out whats going wrong with register or change the RegExp code hint fix to bail
// before HintUtils (Something like the fix for no code hints for multiple selection)
describe("RegExp codehint tests", function () {
it("should not show codehints for regular expression in a script block in html", function () {
var editor,
pos = {line: 8, ch: 30};
// Place cursor inside a sample regular expression
// Note: line for pos is 0-based and editor lines numbers are 1-based
initCodeHintTest("test1.html", pos);
runs(function () {
editor = EditorManager.getCurrentFullEditor();
expect(editor).toBeTruthy();
invokeCodeHints();
expectNoHints();
editor = null;
});
});
it("should not show codehints for regular expression in a Javascript file", function () {
var editor,
pos = {line: 2, ch: 30};
// Place cursor inside a sample regular expression
// Note: line for pos is 0-based and editor lines numbers are 1-based
initCodeHintTest("testRegExp.js", pos);
runs(function () {
editor = EditorManager.getCurrentFullEditor();
expect(editor).toBeTruthy();
invokeCodeHints();
expectNoHints();
editor = null;
});
});
});
describe("Hint Provider Registration", function () {
beforeEach(function () {
initCodeHintTest("test1.html", {line: 0, ch: 0});
});
var mockProvider = {
hasHints: function (editor, implicitChar) {
return true;
},
getHints: function (implicitChar) {
return { hints: ["mock hint"], match: null, selectInitial: false };
},
insertHint: function (hint) { }
};
function expectMockHints() {
var codeHintList = expectSomeHints();
expect(codeHintList.hints[0]).toBe("mock hint");
expect(codeHintList.hints.length).toBe(1);
}
it("should register provider for a new language", function () {
runs(function () {
CodeHintManager.registerHintProvider(mockProvider, ["clojure"], 0);
// Ensure no hints in language we didn't register for
invokeCodeHints();
expectNoHints();
// Expect hints in language we did register for
var promise = CommandManager.execute(Commands.FILE_OPEN, { fullPath: SpecRunnerUtils.makeAbsolute("test.clj") });
waitsForDone(promise);
});
runs(function () {
invokeCodeHints();
expectMockHints();
CodeHintManager._removeHintProvider(mockProvider, ["clojure"], 0);
});
});
it("should register higher-priority provider for existing language", function () {
runs(function () {
CodeHintManager.registerHintProvider(mockProvider, ["html"], 1);
// Expect hints to replace default HTML hints
var editor = EditorManager.getCurrentFullEditor();
editor.setCursorPos(3, 1);
invokeCodeHints();
expectMockHints();
CodeHintManager._removeHintProvider(mockProvider, ["html"], 1);
});
});
it("should register \"all\" languages provider", function () {
runs(function () {
CodeHintManager.registerHintProvider(mockProvider, ["all"], 0);
// Expect hints in language that already had hints (when not colliding with original provider)
invokeCodeHints();
expectMockHints();
// Expect hints in language that had no hints before
var promise = CommandManager.execute(Commands.FILE_OPEN, { fullPath: SpecRunnerUtils.makeAbsolute("test.clj") });
waitsForDone(promise);
});
runs(function () {
invokeCodeHints();
expectMockHints();
CodeHintManager._removeHintProvider(mockProvider, ["all"], 0);
});
});
});
describe("HTML Tests", function () {
it("should show code hints menu and insert text at IP", function () {
var editor,
pos = {line: 3, ch: 1},
lineBefore,
lineAfter;
// minimal markup with an open '<' before IP
// Note: line for pos is 0-based and editor lines numbers are 1-based
initCodeHintTest("test1.html", pos);
runs(function () {
editor = EditorManager.getCurrentFullEditor();
expect(editor).toBeTruthy();
// get text before insert operation
lineBefore = editor.document.getLine(pos.line);
invokeCodeHints();
expectSomeHints();
});
// simulate Enter key to insert code hint into doc
runs(function () {
var e = $.Event("keydown");
e.keyCode = KeyEvent.DOM_VK_RETURN;
editor = EditorManager.getCurrentFullEditor();
expect(editor).toBeTruthy();
CodeHintManager._getCodeHintList()._keydownHook(e);
// doesn't matter what was inserted, but line should be different
var newPos = editor.getCursorPos();
lineAfter = editor.document.getLine(newPos.line);
expect(lineBefore).not.toEqual(lineAfter);
// and popup should auto-close
expectNoHints();
editor = null;
});
});
it("should go to next hint with ctrl+space", function () {
var editor,
pos = {line: 3, ch: 1},
hintBefore,
hintAfter;
// minimal markup with an open '<' before IP
// Note: line for pos is 0-based and editor lines numbers are 1-based
initCodeHintTest("test1.html", pos);
// simulate ctrl+space key to make sure it goes to next hint
runs(function () {
var e = $.Event("keydown");
e.keyCode = KeyEvent.DOM_VK_SPACE;
e.ctrlKey = true;
editor = EditorManager.getCurrentFullEditor();
expect(editor).toBeTruthy();
invokeCodeHints();
var codeHintList = expectSomeHints();
hintBefore = codeHintList.selectedIndex;
// make sure hint list starts at 0
expect(hintBefore).toEqual(0);
// simulate ctrl+space keyhook
CodeHintManager._getCodeHintList()._keydownHook(e);
hintAfter = codeHintList.selectedIndex;
// selectedIndex should be one more after doing ctrl+space key event.
expect(hintBefore).toEqual(hintAfter-1);
editor = null;
});
});
it("should loop to first hint when ctrl+space at last hint", function () {
var editor,
pos = {line: 3, ch: 1},
hintBefore,
hintAfter;
// minimal markup with an open '<' before IP
// Note: line for pos is 0-based and editor lines numbers are 1-based
initCodeHintTest("test1.html", pos);
// simulate ctrl+space key to make sure it goes to next hint
runs(function () {
var e = $.Event("keydown");
e.keyCode = KeyEvent.DOM_VK_UP;
editor = EditorManager.getCurrentFullEditor();
expect(editor).toBeTruthy();
invokeCodeHints();
// simulate up keyhook to send it to last hint
CodeHintManager._getCodeHintList()._keydownHook(e);
var codeHintList = expectSomeHints();
hintBefore = codeHintList.selectedIndex;
var numberOfHints = codeHintList.$hintMenu.find("li").length-1;
// should be at last hint
expect(hintBefore).toEqual(numberOfHints);
// call ctrl+space to loop it to first hint
e.keyCode = KeyEvent.DOM_VK_SPACE;
e.ctrlKey = true;
// simulate ctrl+space keyhook to send it to first hint
CodeHintManager._getCodeHintList()._keydownHook(e);
hintAfter = codeHintList.selectedIndex;
// should now be at hint 0
expect(hintAfter).toEqual(0);
editor = null;
});
});
it("should not show code hints if there is a multiple selection", function () {
// minimal markup with an open '<' before IP
// Note: line for pos is 0-based and editor lines numbers are 1-based
initCodeHintTest("test1.html", [
{start: {line: 3, ch: 1}, end: {line: 3, ch: 1}, primary: true},
{start: {line: 4, ch: 1}, end: {line: 4, ch: 1}}
]);
runs(function () {
invokeCodeHints();
expectNoHints();
});
});
it("should dismiss existing code hints if selection changes to a multiple selection", function () {
var editor;
initCodeHintTest("test1.html", {line: 3, ch: 1});
runs(function () {
editor = EditorManager.getCurrentFullEditor();
expect(editor).toBeTruthy();
invokeCodeHints();
expectSomeHints();
});
runs(function () {
editor.setSelections([
{start: {line: 3, ch: 1}, end: {line: 3, ch: 1}, primary: true},
{start: {line: 4, ch: 1}, end: {line: 4, ch: 1}}
]);
expectNoHints();
});
});
it("should dismiss code hints menu with Esc key", function () {
var pos = {line: 3, ch: 1};
// minimal markup with an open '<' before IP
// Note: line for pos is 0-based and editor lines numbers are 1-based
initCodeHintTest("test1.html", pos);
runs(function () {
invokeCodeHints();
// verify list is open
expectSomeHints();
});
// simulate Esc key to dismiss code hints menu
runs(function () {
var key = KeyEvent.DOM_VK_ESCAPE,
element = testWindow.$(".dropdown.open")[0];
SpecRunnerUtils.simulateKeyEvent(key, "keydown", element);
// verify list is no longer open
expectNoHints();
});
});
it("should dismiss code hints menu when launching a command", function () {
var editor,
pos = {line: 3, ch: 1};
// minimal markup with an open '<' before IP
// Note: line for pos is 0-based and editor lines numbers are 1-based
initCodeHintTest("test1.html", pos);
runs(function () {
editor = EditorManager.getCurrentFullEditor();
expect(editor).toBeTruthy();
editor.document.replaceRange("di", pos);
invokeCodeHints();
// verify list is open
expectSomeHints();
});
// Call Undo command to remove "di" and then verify no code hints
runs(function () {
CommandManager.execute(Commands.EDIT_UNDO);
// verify list is no longer open
expectNoHints();
editor = null;
});
});
it("should stop handling keydowns if closed by a click outside", function () {
var editor,
pos = {line: 3, ch: 1};
// minimal markup with an open '<' before IP
// Note: line for pos is 0-based and editor lines numbers are 1-based
initCodeHintTest("test1.html", pos);
runs(function () {
editor = EditorManager.getCurrentFullEditor();
expect(editor).toBeTruthy();
editor.document.replaceRange("di", pos);
invokeCodeHints();
// verify list is open
expectSomeHints();
// get the document text and make sure it doesn't change if we
// click outside and then keydown
var text = editor.document.getText();
testWindow.$("body").click();
KeyBindingManager._handleKeyEvent({
keyCode: KeyEvent.DOM_VK_ENTER,
stopImmediatePropagation: function () { },
stopPropagation: function () { },
preventDefault: function () { }
});
// Verify that after the keydown, the session is closed
// (not just the hint popup). Because of #1381, we don't
// actually have a way to close the session as soon as the
// popup is dismissed by Bootstrap, so we do so on the next
// keydown. Eventually, once that's fixed, we should be able
// to move this expectNoHints() up after the click.
expectNoHints();
expect(editor.document.getText()).toEqual(text);
editor = null;
});
});
});
});
});