// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed 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.
package com.google.collide.client.code.autocomplete.codemirror;
import static com.google.collide.client.code.autocomplete.TestUtils.CTRL_SPACE;
import static com.google.gwt.event.dom.client.KeyCodes.KEY_BACKSPACE;
import static org.waveprotocol.wave.client.common.util.SignalEvent.KeySignalType.DELETE;
import com.google.collide.client.code.autocomplete.AutocompleteProposals;
import com.google.collide.client.code.autocomplete.AutocompleteProposals.ProposalWithContext;
import com.google.collide.client.code.autocomplete.AutocompleteResult;
import com.google.collide.client.code.autocomplete.DefaultAutocompleteResult;
import com.google.collide.client.code.autocomplete.LanguageSpecificAutocompleter;
import com.google.collide.client.code.autocomplete.LanguageSpecificAutocompleter.ExplicitAction;
import com.google.collide.client.code.autocomplete.LanguageSpecificAutocompleter.ExplicitActionType;
import com.google.collide.client.code.autocomplete.MockAutocompleterEnvironment;
import com.google.collide.client.code.autocomplete.MockAutocompleterEnvironment.MockAutocompleter;
import com.google.collide.client.code.autocomplete.SignalEventEssence;
import com.google.collide.client.code.autocomplete.TestUtils;
import com.google.collide.client.editor.Editor;
import com.google.collide.client.editor.input.TestSignalEvent;
import com.google.collide.client.testutil.CodeMirrorTestCase;
import com.google.collide.client.testutil.TestSchedulerImpl;
import com.google.collide.client.util.PathUtil;
import com.google.collide.client.util.input.ModifierKeys;
import com.google.collide.json.shared.JsonArray;
import com.google.collide.json.shared.JsonStringSet;
import com.google.collide.shared.document.Document;
import com.google.collide.shared.document.util.LineUtils;
import com.google.collide.shared.util.JsonCollections;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.dom.client.KeyCodes;
import org.waveprotocol.wave.client.common.util.SignalEvent.KeySignalType;
import javax.annotation.Nullable;
/**
* Test for JS autocompletion cases, when CodeMirror parser is used.
*
*/
public class JsCodemirrorTest extends CodeMirrorTestCase {
static final SignalEventEssence DELETE_KEY = new SignalEventEssence(
KEY_BACKSPACE, false, false, false, false, DELETE);
private static final SignalEventEssence ENTER = new SignalEventEssence(
KeyCodes.KEY_ENTER, false, false, false, false, KeySignalType.INPUT);
@Override
public String getModuleName() {
return "com.google.collide.client.TestCode";
}
public void testNoProposalsInCommentsAndStrings() {
// 0 1 2
// 012345678901234567890123456789
String text1 = "var a = 'Hello Kitty'; // Funny?";
String text2 = "var b = /* Aha =) */ 0;";
checkHasProposals(text1, 0, true, "global");
checkHasProposals(text1, 8, true, "before string");
checkHasProposals(text1, 9, false, "string began");
checkHasProposals(text1, 10, false, "in string");
checkHasProposals(text1, 15, false, "in string after space");
checkHasProposals(text1, 21, true, "after string");
checkHasProposals(text1, 22, true, "after string");
checkHasProposals(text1, 23, true, "before comment");
checkHasProposals(text1, 26, false, "in comment after space");
checkHasProposals(text1, 30, false, "in comment");
checkHasProposals(text2, 0, true, "after comment");
checkHasProposals(text2, 8, true, "before multiline");
checkHasProposals(text2, 13, false, "in multiline");
checkHasProposals(text2, 15, false, "in multiline after space");
checkHasProposals(text2, 21, true, "after multiline");
}
private void checkHasProposals(String text, int column,
boolean expectHasProposals, String message) {
MockAutocompleterEnvironment helper = new MockAutocompleterEnvironment();
helper.setup(new PathUtil("foo.js"), text, 0, column, true);
AutocompleteProposals proposals = helper.autocompleter.jsAutocompleter.findAutocompletions(
helper.editor.getSelection(), CTRL_SPACE);
assertEquals(message, expectHasProposals, !proposals.isEmpty());
}
public void testNoTemplateProposalsAfterThis() {
MockAutocompleterEnvironment helper = new MockAutocompleterEnvironment();
helper.setup(new PathUtil("foo.js"), "this.", 0, 5, true);
AutocompleteProposals proposals = helper.autocompleter.jsAutocompleter.findAutocompletions(
helper.editor.getSelection(), CTRL_SPACE);
assertTrue("has no proposals", proposals.isEmpty());
}
public void testTemplateProposalsInGlobal() {
MockAutocompleterEnvironment helper = new MockAutocompleterEnvironment();
helper.setup(new PathUtil("foo.js"), "", 0, 0, true);
AutocompleteProposals autocompletions =
helper.autocompleter.jsAutocompleter.findAutocompletions(
helper.editor.getSelection(), CTRL_SPACE);
assertFalse("has proposals", autocompletions.isEmpty());
ProposalWithContext whileProposal = TestUtils.selectProposalByName(autocompletions, "while");
assertNotNull("has 'while'", whileProposal);
helper.autocompleter.reallyFinishAutocompletion(whileProposal);
assertEquals("while () {\n \n}", helper.editor.getDocument().asText());
}
public void testAutocompletionAfterKeyword() {
MockAutocompleterEnvironment helper = new MockAutocompleterEnvironment();
helper.setup(new PathUtil("foo.js"), "for", 0, 3, true);
AutocompleteProposals autocompletions =
helper.autocompleter.jsAutocompleter.findAutocompletions(
helper.editor.getSelection(), CTRL_SPACE);
ProposalWithContext proposal = autocompletions.select(0);
assertEquals("proposal name", "for", proposal.getItem().getName());
helper.autocompleter.reallyFinishAutocompletion(proposal);
String text = helper.editor.getDocument().getFirstLine().getText();
assertEquals("resulting text", "for (;;) {\n", text);
}
public void testDoNotDieOnLongLines() {
String longLine = " ";
for (int i = 0; i < 12; i++) {
longLine = longLine + longLine;
}
// longLine length is 4096
MockAutocompleterEnvironment helper = new MockAutocompleterEnvironment();
String text = "function foo() {\n"
+ " var bar1;\n"
+ " var bar2 ='" + longLine + "';\n"
+ " var bar3;\n"
+ " " // Cursor here.
+ "}";
helper.setup(new PathUtil("foo.js"), text, 4, 2, true);
helper.parser.begin();
helper.parseScheduler.requests.get(0).run(10);
AutocompleteProposals autocompletions =
helper.autocompleter.jsAutocompleter.findAutocompletions(
helper.editor.getSelection(), CTRL_SPACE);
JsonStringSet proposals = JsonCollections.createStringSet();
for (int i = 0, l = autocompletions.size(); i < l; i++) {
proposals.add(autocompletions.get(i).getName());
}
assertTrue("contains var defined before long line", proposals.contains("bar1"));
assertTrue("contains var defined after long line", proposals.contains("bar3"));
}
public void testDoNotDieOnRegExp() {
MockAutocompleterEnvironment helper = new MockAutocompleterEnvironment();
String text = "function foo() {\n"
+ " var bar1 = /regexp/;\n"
+ " var bar2;\n"
+ " " // Cursor here.
+ "}";
helper.setup(new PathUtil("foo.js"), text, 3, 2, true);
helper.parser.begin();
helper.parseScheduler.requests.get(0).run(10);
AutocompleteProposals autocompletions =
helper.autocompleter.jsAutocompleter.findAutocompletions(
helper.editor.getSelection(), CTRL_SPACE);
JsonStringSet proposals = JsonCollections.createStringSet();
for (int i = 0, l = autocompletions.size(); i < l; i++) {
proposals.add(autocompletions.get(i).getName());
}
assertTrue("contains var defined in line with regexp", proposals.contains("bar1"));
assertTrue("contains var defined after line with regexp", proposals.contains("bar2"));
}
public void testDeleteAtBeginningOfDocument() {
MockAutocompleterEnvironment helper = new MockAutocompleterEnvironment();
String text = "<cursorAtTheBeginingOfFirstLine>";
helper.setup(new PathUtil("foo.js"), text, 0, 0, true);
ExplicitAction action = helper.autocompleter.jsAutocompleter.getExplicitAction(
helper.editor.getSelection(), DELETE_KEY, false);
assertEquals(LanguageSpecificAutocompleter.ExplicitActionType.DEFAULT, action.getType());
}
public void testClosePopupOnSpace() {
MockAutocompleterEnvironment helper = new MockAutocompleterEnvironment();
// TODO: vars in the global scope are not registered by CM.
String text = "function a() { var abba, apple, arrow; a";
helper.setup(new PathUtil("foo.js"), text, 0, text.length(), true);
final MockAutocompleter autocompleter = helper.autocompleter;
assertFalse("initially popup is not shown", helper.popup.isShowing());
final JsonArray<Scheduler.ScheduledCommand> scheduled = JsonCollections.createArray();
// We want to click ctrl-space.
Runnable ctrlSpaceClicker = new Runnable() {
@Override
public void run() {
autocompleter.pressKey(CTRL_SPACE);
}
};
// Collect deferred tasks in array.
TestSchedulerImpl.AngryScheduler scheduler = new TestSchedulerImpl.AngryScheduler() {
@Override
public void scheduleDeferred(ScheduledCommand scheduledCommand) {
scheduled.add(scheduledCommand);
}
};
// Now, if we hit ctrl-space - popup will appear with 3 variables.
TestSchedulerImpl.runWithSpecificScheduler(ctrlSpaceClicker, scheduler);
assertEquals("actual autocompletion is deferred", 1, scheduled.size());
// Now autocompletion acts.
scheduled.get(0).execute();
assertTrue("popup appeared", helper.popup.isShowing());
assertEquals("variables are proposed", 4, helper.popup.proposals.size());
// Now, if we type " " autocompletion popup should disappear.
autocompleter.pressKey(new SignalEventEssence(' '));
assertFalse("popup disappeared", helper.popup.isShowing());
}
public void testRawExplicitInContext() {
SignalEventEssence quoteKey = new SignalEventEssence('"');
checkExplicit("line-comment", "// var a =", 0, quoteKey, null);
checkExplicit("in-block-comment", "/* var a = */", 3, quoteKey, null);
checkExplicit("block-comment-eol", "/* var a =\nsecond line*/", 0, quoteKey, null);
checkExplicit("in-string", "var a =''", 1, quoteKey, null);
checkExplicit("bs-in-string", "var a ='\"\"'", 2, DELETE_KEY, null);
checkExplicit("bs-in-comment", "// var a =''", 1, DELETE_KEY, null);
checkExplicit("bs-between-string", "var a ='''", 1, DELETE_KEY, null);
}
public void testBracesPairing() {
checkExplicit("braces-pairing", "foo", 0, new SignalEventEssence('['),
new DefaultAutocompleteResult("[]", "", 1));
}
public void testBracesNotPairing() {
checkExplicit("braces-not-pairing", "foo", 3, new SignalEventEssence('['), null);
}
public void testQuotesPairing() {
checkExplicit("quotes-pairing", "", 0, new SignalEventEssence('"'),
new DefaultAutocompleteResult("\"\"", "", 1));
}
public void testQuotesNotPairing() {
checkExplicit("quotes-not-pairing", "\"\"", 0, new SignalEventEssence('"'), null);
}
public void testBracesPassing() {
checkExplicit(
"braces-passing", "foo[]", 1, new SignalEventEssence(']'),
DefaultAutocompleteResult.PASS_CHAR);
}
public void testBracesNotPassing() {
checkExplicit("braces-not-passing", "[(()]", 2, new SignalEventEssence(')'), null);
}
public void testSymmetricDeletion() {
DefaultAutocompleteResult bsDelete = new DefaultAutocompleteResult(
"", 0, 1, 0, 1, null, "");
checkExplicit("bs-after-comment", "/* Q */ var a =''", 1, DELETE_KEY, bsDelete);
checkExplicit("bs-braces", "foo[]", 1, DELETE_KEY, bsDelete);
}
public void testEnterBetweenCurlyBraces() {
checkExplicit("enter-between-curly-braces", " {}", 1, ENTER,
new DefaultAutocompleteResult("\n \n ", "", 5));
}
private void checkExplicit(String message, String text, int tailOffset,
SignalEventEssence trigger, @Nullable DefaultAutocompleteResult expected) {
MockAutocompleterEnvironment helper = new MockAutocompleterEnvironment();
Document document = Document.createFromString(text);
int column = LineUtils.getLastCursorColumn(document.getFirstLine()) - tailOffset;
helper.setup(new PathUtil("foo.js"), document, 0, column, true);
ExplicitAction action = helper.autocompleter.jsAutocompleter.getExplicitAction(
helper.editor.getSelection(), trigger, false);
AutocompleteResult commonResult = action.getExplicitAutocompletion();
if (expected == null) {
assertNull("result", commonResult);
assertFalse("action", ExplicitActionType.EXPLICIT_COMPLETE == action.getType());
return;
} else {
assertTrue("action", ExplicitActionType.EXPLICIT_COMPLETE == action.getType());
}
assertTrue("result type", commonResult instanceof DefaultAutocompleteResult);
DefaultAutocompleteResult result = (DefaultAutocompleteResult) commonResult;
assertNotNull(message + ":result", result);
assertEquals(message + ":text",
expected.getAutocompletionText(), result.getAutocompletionText());
assertEquals(message + ":delete", expected.getDeleteCount(), result.getDeleteCount());
assertEquals(message + ":bspace", expected.getBackspaceCount(), result.getBackspaceCount());
assertEquals(message + ":jump", expected.getJumpLength(), result.getJumpLength());
}
/**
* Integration test: check that
* {@link com.google.collide.client.code.autocomplete.codegraph.CodeGraphAutocompleter}
* allows
* {@link com.google.collide.client.editor.input.DefaultScheme} to
* perform specific textual changes.
*/
public void testDoNotPreventCtrlBs() {
String text = "#!@abc ";
MockAutocompleterEnvironment helper = new MockAutocompleterEnvironment();
helper.setup(new PathUtil("test.js"), text, 0, text.length(), true);
final Editor editor = helper.editor;
final JsonArray<Scheduler.ScheduledCommand> scheduled = JsonCollections.createArray();
TestSchedulerImpl.AngryScheduler scheduler = new TestSchedulerImpl.AngryScheduler() {
@Override
public void scheduleDeferred(ScheduledCommand scheduledCommand) {
scheduled.add(scheduledCommand);
}
};
Runnable ctrlBsClicker = new Runnable() {
@Override
public void run() {
TestSignalEvent bsTrigger = new TestSignalEvent(
KeyCodes.KEY_BACKSPACE, KeySignalType.DELETE, ModifierKeys.ACTION);
editor.getInput().processSignalEvent(bsTrigger);
}
};
TestSchedulerImpl.runWithSpecificScheduler(ctrlBsClicker, scheduler);
while (!scheduled.isEmpty()) {
Scheduler.ScheduledCommand command = scheduled.remove(0);
command.execute();
}
String result2 = editor.getDocument().getFirstLine().getText();
assertEquals("after ctrl-BS", "#!@", result2);
}
}