src/extensions/default/PrefsCodeHints/unittests.js
/*
* Copyright (c) 2015 - 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, expect*/
define(function (require, exports, module) {
"use strict";
// Load dependencies.
var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"),
PrefsCodeHints = require("./main"),
testPreferences = JSON.parse(require("text!./unittest-files/preferences.json"));
describe("Preferences Code Hints", function () {
var testContent, testDocument, testEditor, mockEditor;
// A sample preferences file to run tests against.
testContent = "{\n" +
" \"closeBrackets\": true,\n" +
" \"insertHintOnTab\": false,\n" +
" \"language.fileExtensions\": {\n" +
" \"txt\": \"markdown\"\n" +
" },\n" +
" \"language.fileNames\": {\n" +
" \"README.txt\": \"markdown\"\n" +
" },\n" +
" \"language\": {\n" +
" \"javascript\": {\n" +
" \"spaceUnits\": 4,\n" +
" \"useTabChar\": false\n" +
" },\n" +
" \"php\": {\n" +
" \"tabSize\": 4,\n" +
" \"useTabChar\":true,\n" +
" \"closeBrackets\":false\n" +
" }\n" +
" },\n" +
" \"jslint.options\": {\n" +
" \"devel\": true,\n" +
" \"regexp\": true\n" +
" },\n" +
" \"linting.prefer\": [\"JSHint\",\"JSLint\"],\n" +
" \n" +
" \"linting.usePreferredOnly\" false," +
" \n" +
"}";
beforeEach(function () {
mockEditor = SpecRunnerUtils.createMockEditor(testContent, "json", {
startLine: 0,
endLine: 30
});
testEditor = mockEditor.editor;
testDocument = mockEditor.doc;
// Rename the file to preferences file.
testDocument.file._name = ".brackets.json";
// Setup a test environment by loading minimum preferences required to run unit tests.
PrefsCodeHints._setupTestEnvironment(testDocument, testPreferences);
});
afterEach(function () {
testEditor.destroy();
testDocument = null;
});
// Extracts hints out of their DOM nodes.
function extractHintList(hintList) {
return $.map(hintList, function ($node) {
return $node.find(".hint-obj").text();
});
}
// Determines the availability of hints.
function expectHints(provider) {
expect(provider.hasHints(testEditor, null)).toBe(true);
var hintObj = provider.getHints();
expect(hintObj).toBeTruthy();
return hintObj.hints;
}
// Determines the non-availability of hints.
function expectNoHints(provider) {
var hasHints = provider.hasHints(testEditor, null);
if (!hasHints) {
expect(hasHints).toBe(false);
} else {
expect(provider.getHints(null)).toBe(null);
}
}
// Determines the presence of a hint in the hint list.
function verifyHints(hintList, expectedHint) {
var hints = extractHintList(hintList);
expect(hints[0]).toBe(expectedHint);
}
// Determines the exclusion of a hint in the hint list.
function verifyHintsExcluded(hintList, unexpectedHint) {
var hints = extractHintList(hintList);
expect(hints.indexOf(unexpectedHint)).toBe(-1);
}
// Inserts the selected hint.
function selectHint(provider, expectedHint) {
var hintList = expectHints(provider),
hints = extractHintList(hintList),
index = hints.indexOf(expectedHint);
expect(index).not.toBe(-1);
return provider.insertHint(hintList[index]);
}
// Determines a token at any given position.
function expectTokenAt(pos, string, type) {
var token = testEditor._codeMirror.getTokenAt(pos);
expect(token.string).toBe(string);
expect(token.type).toBe(type);
}
// Helper functions for testing cursor position / selection range
function fixPos(pos) {
if (!("sticky" in pos)) {
pos.sticky = null;
}
return pos;
}
// Determines the position of the cursor.
function expectCursorAt(pos) {
var selection = testEditor.getSelection();
expect(fixPos(selection.start)).toEqual(fixPos(selection.end));
expect(fixPos(selection.start)).toEqual(fixPos(pos));
}
describe("File name based hinting", function () {
it("it should hint in .brackets.json", function () {
// Between " and closeBrackets"
testEditor.setCursorPos({line: 1, ch: 5});
expectHints(PrefsCodeHints.hintProvider);
testEditor.setCursorPos({line: 1, ch: 20});
expectHints(PrefsCodeHints.hintProvider);
});
it("it should hint in brackets.json", function () {
testDocument.file._name = "brackets.json";
PrefsCodeHints._setupTestEnvironment(testDocument, testPreferences);
// Between " and closeBrackets"
testEditor.setCursorPos({line: 1, ch: 5});
expectHints(PrefsCodeHints.hintProvider);
// After "closeBrackets":
testEditor.setCursorPos({line: 1, ch: 20});
expectHints(PrefsCodeHints.hintProvider);
});
it("it should NOT hint in other JSON files", function () {
testDocument.file._name = "package.json";
PrefsCodeHints._setupTestEnvironment(testDocument, testPreferences);
// Between " and closeBrackets"
testEditor.setCursorPos({line: 1, ch: 5});
expectNoHints(PrefsCodeHints.hintProvider);
// After "closeBrackets":
testEditor.setCursorPos({line: 1, ch: 20});
expectNoHints(PrefsCodeHints.hintProvider);
});
});
describe("Key Hints", function () {
it("should hint at the begininng of a key", function () {
var hintList;
// Between " and language"
testEditor.setCursorPos({line: 9, ch: 5});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "closeOthers.above");
// Between " and javascript"
testEditor.setCursorPos({line: 10, ch: 9});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "audio");
// Between " and spaceUnits"
testEditor.setCursorPos({line: 11, ch: 13});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "closeBrackets");
});
it("should hint in the center of a key", function () {
var hintList;
// In "language"
testEditor.setCursorPos({line: 9, ch: 9});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "language");
// In "javascript"
testEditor.setCursorPos({line: 10, ch: 14});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "javascript");
// In "spaceUnits"
testEditor.setCursorPos({line: 11, ch: 18});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "spaceUnits");
});
it("should hint at the end of a key", function () {
var hintList;
// Between "language and "
testEditor.setCursorPos({line: 9, ch: 13});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "language");
// Between "javascript and "
testEditor.setCursorPos({line: 10, ch: 19});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "javascript");
// Between "spaceUnits and "
testEditor.setCursorPos({line: 11, ch: 23});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "spaceUnits");
});
it("should NOT hint for blacklisted parent keys", function () {
// Between " and txt"
testEditor.setCursorPos({line: 4, ch: 9});
expectNoHints(PrefsCodeHints.hintProvider);
// In "txt"
testEditor.setCursorPos({line: 4, ch: 11});
expectNoHints(PrefsCodeHints.hintProvider);
// Between " and README.txt"
testEditor.setCursorPos({line: 7, ch: 9});
expectNoHints(PrefsCodeHints.hintProvider);
// In "README.txt"
testEditor.setCursorPos({line: 7, ch: 15});
expectNoHints(PrefsCodeHints.hintProvider);
});
it("should NOT hint before initial quote of a key", function () {
// Before "closeBrackets"
testEditor.setCursorPos({line: 1, ch: 4});
expectNoHints(PrefsCodeHints.hintProvider);
// Before "language"
testEditor.setCursorPos({line: 9, ch: 4});
expectNoHints(PrefsCodeHints.hintProvider);
// Before "javascript"
testEditor.setCursorPos({line: 10, ch: 8});
expectNoHints(PrefsCodeHints.hintProvider);
// Before "spaceUnits"
testEditor.setCursorPos({line: 11, ch: 12});
expectNoHints(PrefsCodeHints.hintProvider);
});
it("should NOT hint after final quote of a key", function () {
// After "closeBrackets"
testEditor.setCursorPos({line: 1, ch: 19});
expectNoHints(PrefsCodeHints.hintProvider);
// After "language"
testEditor.setCursorPos({line: 9, ch: 14});
expectNoHints(PrefsCodeHints.hintProvider);
// After "javascript"
testEditor.setCursorPos({line: 10, ch: 20});
expectNoHints(PrefsCodeHints.hintProvider);
// After "spaceUnits"
testEditor.setCursorPos({line: 11, ch: 24});
expectNoHints(PrefsCodeHints.hintProvider);
});
it("should NOT include keys already used in the context of current object", function () {
var hintList;
// Between " and insertHintOnTab"
testEditor.setCursorPos({line: 2, ch: 5});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHintsExcluded(hintList, "closeBrackets");
verifyHintsExcluded(hintList, "language.fileExtensions");
verifyHintsExcluded(hintList, "language.fileNames");
verifyHintsExcluded(hintList, "language");
verifyHintsExcluded(hintList, "jslint.options");
verifyHintsExcluded(hintList, "linting.prefer");
verifyHintsExcluded(hintList, "linting.usePreferredOnly");
// Between " and insertHintOnTab"
testEditor.setCursorPos({line: 15, ch: 13});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHintsExcluded(hintList, "useTabChar");
verifyHintsExcluded(hintList, "closeBrackets");
});
});
describe("Value Hints", function () {
it("should hint after a colon", function () {
var hintList;
// After "closeBrackets":
testEditor.setCursorPos({line: 1, ch: 20});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "false");
// After "insertHintOnTab":
testEditor.setCursorPos({line: 2, ch: 22});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "false");
// After "useTabChar":
testEditor.setCursorPos({line: 12, ch: 25});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "false");
});
it("should hint after after a space followed by a colon", function () {
var hintList;
// After "closeBrackets":+space
testEditor.setCursorPos({line: 1, ch: 21});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "false");
// After "insertHintOnTab":+space
testEditor.setCursorPos({line: 2, ch: 23});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "false");
// After "useTabChar":+space
testEditor.setCursorPos({line: 12, ch: 26});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "false");
});
it("should hint even if space is missing after a colon", function () {
var hintList;
// After "useTabChar":
testEditor.setCursorPos({line: 16, ch: 25});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "false");
// After "closeBrackets":
testEditor.setCursorPos({line: 17, ch: 28});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "false");
});
it("should hint at the beginning of value", function () {
var hintList;
// After "closeBrackets": t
testEditor.setCursorPos({line: 1, ch: 22});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "true");
// After "insertHintOnTab": f
testEditor.setCursorPos({line: 2, ch: 24});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "false");
// After "useTabChar": f
testEditor.setCursorPos({line: 12, ch: 27});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "false");
});
it("should hint at the center of value", function () {
var hintList;
// After "closeBrackets": tru
testEditor.setCursorPos({line: 1, ch: 24});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "true");
// After "insertHintOnTab": fal
testEditor.setCursorPos({line: 2, ch: 26});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "false");
// After "useTabChar": fal
testEditor.setCursorPos({line: 12, ch: 29});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "false");
});
it("should hint at the end of the value", function () {
var hintList;
// After "closeBrackets": true
testEditor.setCursorPos({line: 1, ch: 25});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "true");
// After "insertHintOnTab": false
testEditor.setCursorPos({line: 2, ch: 28});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "false");
// After "useTabChar": false
testEditor.setCursorPos({line: 12, ch: 31});
hintList = expectHints(PrefsCodeHints.hintProvider);
verifyHints(hintList, "false");
});
it("should NOT hint if the corresponding colon is missing", function () {
// After "linting.usePreferredOnly"
testEditor.setCursorPos({line: 26, ch: 30});
expectNoHints(PrefsCodeHints.hintProvider);
// After "linting.usePreferredOnly"+space
testEditor.setCursorPos({line: 26, ch: 31});
expectNoHints(PrefsCodeHints.hintProvider);
// After "linting.usePreferredOnly" f
testEditor.setCursorPos({line: 26, ch: 32});
expectNoHints(PrefsCodeHints.hintProvider);
// After "linting.usePreferredOnly" fal
testEditor.setCursorPos({line: 26, ch: 34});
expectNoHints(PrefsCodeHints.hintProvider);
// After "linting.usePreferredOnly" false
testEditor.setCursorPos({line: 26, ch: 36});
expectNoHints(PrefsCodeHints.hintProvider);
});
it("should NOT hint after a comma", function () {
// After "closeBrackets": true,
testEditor.setCursorPos({line: 1, ch: 26});
expectNoHints(PrefsCodeHints.hintProvider);
// After "insertHintOnTab": false,
testEditor.setCursorPos({line: 2, ch: 29});
expectNoHints(PrefsCodeHints.hintProvider);
// After "javascript": { [rules] },
testEditor.setCursorPos({line: 13, ch: 10});
expectNoHints(PrefsCodeHints.hintProvider);
// After "language": { [languages] },
testEditor.setCursorPos({line: 19, ch: 6});
expectNoHints(PrefsCodeHints.hintProvider);
});
it("should NOT hint before opening braces", function () {
// Between "javascript": and {
testEditor.setCursorPos({line: 10, ch: 22});
expectNoHints(PrefsCodeHints.hintProvider);
// After "javascript": {
testEditor.setCursorPos({line: 10, ch: 23});
expectNoHints(PrefsCodeHints.hintProvider);
});
it("should NOT hint after closing braces", function () {
// After "javascript": { [rules] }
testEditor.setCursorPos({line: 13, ch: 9});
expectNoHints(PrefsCodeHints.hintProvider);
// After "language": { [languages] }
testEditor.setCursorPos({line: 19, ch: 5});
expectNoHints(PrefsCodeHints.hintProvider);
});
it("should NOT hint before opening brackets", function () {
testEditor.setCursorPos({line: 10, ch: 22});
expectNoHints(PrefsCodeHints.hintProvider);
});
it("should NOT hint after closing brackets", function () {
testEditor.setCursorPos({line: 10, ch: 22});
expectNoHints(PrefsCodeHints.hintProvider);
});
});
describe("Key Insertion", function () {
it("should enter entire key after initial quote is typed", function () {
testDocument.replaceRange("\"", {line: 25, ch: 4});
testEditor.setCursorPos({line: 25, ch: 5});
selectHint(PrefsCodeHints.hintProvider, "closeOthers.above");
expectTokenAt({line: 25, ch: 22}, "\"closeOthers.above\"", "string property");
expectTokenAt({line: 25, ch: 24}, ":", null);
expectCursorAt({line: 25, ch: 25});
});
it("should enter entire key after first letter of a key is typed", function () {
testDocument.replaceRange("\"c", {line: 25, ch: 4});
testEditor.setCursorPos({line: 25, ch: 6});
selectHint(PrefsCodeHints.hintProvider, "closeOthers.above");
expectTokenAt({line: 25, ch: 22}, "\"closeOthers.above\"", "string property");
expectTokenAt({line: 25, ch: 24}, ":", null);
expectCursorAt({line: 25, ch: 25});
});
it("should enter entire key after few initial letters are typed", function () {
testDocument.replaceRange("\"close", {line: 25, ch: 4});
testEditor.setCursorPos({line: 25, ch: 10});
selectHint(PrefsCodeHints.hintProvider, "closeOthers.above");
expectTokenAt({line: 25, ch: 22}, "\"closeOthers.above\"", "string property");
expectTokenAt({line: 25, ch: 24}, ":", null);
expectCursorAt({line: 25, ch: 25});
});
it("should replace existing key after few initial letter are typed", function () {
testDocument.replaceRange("\"closeOthers.above\": true,", {line: 25, ch: 4});
testEditor.setCursorPos({line: 25, ch: 17});
selectHint(PrefsCodeHints.hintProvider, "closeOthers.below");
expectTokenAt({line: 25, ch: 22}, "\"closeOthers.below\"", "string property");
expectTokenAt({line: 25, ch: 24}, ":", null);
expectCursorAt({line: 25, ch: 23});
});
it("should append braces to a hint in case it is an object", function () {
testDocument.replaceRange("\"", {line: 25, ch: 4});
testEditor.setCursorPos({line: 25, ch: 5});
selectHint(PrefsCodeHints.hintProvider, "closeTags");
expectTokenAt({line: 25, ch: 14}, "\"closeTags\"", "string property");
expectTokenAt({line: 25, ch: 16}, ":", null);
expectTokenAt({line: 25, ch: 18}, "{", null);
expectTokenAt({line: 25, ch: 19}, "}", null);
expectCursorAt({line: 25, ch: 18});
});
it("should append brackets to a hint in case it in an array", function () {
testDocument.replaceRange("\"closeTags\": { \"\" }", {line: 25, ch: 4});
testEditor.setCursorPos({line: 25, ch: 20});
selectHint(PrefsCodeHints.hintProvider, "indentTags");
expectTokenAt({line: 25, ch: 30}, "\"indentTags\"", "string property");
expectTokenAt({line: 25, ch: 32}, ":", null);
expectTokenAt({line: 25, ch: 34}, "[", null);
expectTokenAt({line: 25, ch: 35}, "]", null);
expectCursorAt({line: 25, ch: 34});
});
it("should NOT replace colon and braces/brackets if they already exists", function () {
// After close in "closeBrackets": true
testEditor.setCursorPos({line: 1, ch: 10});
expectTokenAt({line: 1, ch: 20}, ":", null);
selectHint(PrefsCodeHints.hintProvider, "closeOthers.above");
expectTokenAt({line: 1, ch: 24}, ":", null);
});
});
describe("Value Insertion", function () {
it("should insert a value of type Boolean", function () {
testDocument.replaceRange("\"closeOthers.above\":,", {line: 25, ch: 4});
testEditor.setCursorPos({line: 25, ch: 24});
selectHint(PrefsCodeHints.hintProvider, "true");
expectTokenAt({line: 25, ch: 28}, "true", "atom");
expectCursorAt({line: 25, ch: 28});
});
it("should replace current token when editing", function () {
testDocument.replaceRange("\"closeOthers.above\": tru", {line: 25, ch: 4});
testEditor.setCursorPos({line: 25, ch: 28});
selectHint(PrefsCodeHints.hintProvider, "true");
expectTokenAt({line: 25, ch: 28}, "true", "atom");
expectCursorAt({line: 25, ch: 29});
});
it("should insert a value of type String", function () {
testDocument.replaceRange(",\n \"pavement\":", {line: 7, ch: 32});
testEditor.setCursorPos({line: 8, ch: 19});
selectHint(PrefsCodeHints.hintProvider, "python");
expectTokenAt({line: 8, ch: 27}, "\"python\"", "string");
expectCursorAt({line: 8, ch: 27});
});
it("should insert a value if the next token is also a value", function () {
// Before true
testEditor.setCursorPos({line: 1, ch: 21});
selectHint(PrefsCodeHints.hintProvider, "false");
expectTokenAt({line: 1, ch: 26}, "false", "atom");
expectCursorAt({line: 1, ch: 26});
// Before "JSHint"
testEditor.setCursorPos({line: 24, ch: 23});
selectHint(PrefsCodeHints.hintProvider, "JSLint");
expectTokenAt({line: 24, ch: 31}, "\"JSLint\"", "string");
expectCursorAt({line: 24, ch: 31});
});
});
});
});