// 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.html;
import static com.google.collide.client.code.autocomplete.TestUtils.CTRL_SPACE;
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.MockAutocompleterEnvironment;
import com.google.collide.client.code.autocomplete.TestUtils;
import com.google.collide.client.code.autocomplete.integration.DocumentParserListenerAdapter;
import com.google.collide.client.testutil.SynchronousTestCase;
import com.google.collide.client.testutil.TestSchedulerImpl;
import com.google.collide.client.util.IncrementalScheduler;
import com.google.collide.client.util.PathUtil;
import com.google.collide.client.util.collections.SimpleStringBag;
import com.google.collide.codemirror2.CodeMirror2;
import com.google.collide.codemirror2.State;
import com.google.collide.codemirror2.Stream;
import com.google.collide.codemirror2.SyntaxType;
import com.google.collide.codemirror2.Token;
import com.google.collide.codemirror2.TokenType;
import com.google.collide.json.shared.JsonArray;
import com.google.collide.json.shared.JsonStringMap;
import com.google.collide.shared.Pair;
import com.google.collide.shared.document.Document;
import com.google.collide.shared.document.Line;
import com.google.collide.shared.document.anchor.Anchor;
import com.google.collide.shared.document.anchor.AnchorManager;
import com.google.collide.shared.util.JsonCollections;
import com.google.common.base.Preconditions;
/**
* Tests for html autocompletion.
*/
public class HtmlAutocompleteTest extends SynchronousTestCase {
private MockAutocompleterEnvironment helper;
private PathUtil path;
private JsonStringMap<JsonArray<Token>> parsedLines;
private JsonArray<Token> lineTokens;
@Override
public String getModuleName() {
return "com.google.collide.client.TestCode";
}
@Override
public void gwtSetUp() throws Exception {
super.gwtSetUp();
helper = new MockAutocompleterEnvironment();
path = new PathUtil("/test.html");
lineTokens = JsonCollections.createArray();
parsedLines = JsonCollections.createMap();
helper.specificParser = new TestUtils.MockParser(SyntaxType.HTML) {
@Override
public void parseNext(Stream stream, State parserState, JsonArray<Token> tokens) {
Preconditions.checkState(stream instanceof TestUtils.MockStream);
JsonArray<Token> lineTokens = parsedLines.get(((TestUtils.MockStream) stream).getText());
if (lineTokens != null) {
tokens.addAll(lineTokens);
}
}
};
}
private AutocompleteProposals findAutocompletions() {
prepareAutocompleter();
return helper.autocompleter.htmlAutocompleter.findAutocompletions(
helper.editor.getSelection(), CTRL_SPACE);
}
private void parseOneLine() {
Runnable runnable = new Runnable() {
@Override
public void run() {
JsonArray<IncrementalScheduler.Task> requests = helper.parseScheduler.requests;
if (!requests.peek().run(1)) {
requests.pop();
}
}
};
TestSchedulerImpl.AngryScheduler scheduler = new TestSchedulerImpl.AngryScheduler() {
@Override
public void scheduleDeferred(ScheduledCommand scheduledCommand) {
scheduledCommand.execute();
}
};
TestSchedulerImpl.runWithSpecificScheduler(runnable, scheduler);
}
private void prepareAutocompleter() {
String text = tokensToText(lineTokens);
parsedLines.put(text, lineTokens);
helper.setup(path, text, 0, text.length(), false);
helper.parser.getListenerRegistrar().add(new DocumentParserListenerAdapter(
helper.autocompleter, helper.editor));
helper.parser.begin();
parseOneLine();
}
private String tokensToText(JsonArray<Token> tokens) {
StringBuilder builder = new StringBuilder();
for (Token token : tokens.asIterable()) {
builder.append(token.getValue());
}
return builder.toString();
}
/**
* Tests attributes proposals.
*/
public void testHtmlAttributes() {
HtmlTagsAndAttributes htmlAttributes = HtmlTagsAndAttributes.getInstance();
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.TAG, "<html"));
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.WHITESPACE, " "));
AutocompleteProposals proposals = findAutocompletions();
assertEquals(htmlAttributes.searchAttributes("html", new SimpleStringBag(), "").size(),
proposals.size());
assertEquals("accesskey", proposals.get(0).getName());
}
/**
* Tests that proposal list is updated when parsing of tag is finished.
*/
public void testUpdateOnTagFinish() {
String sampleAttribute = "accesskey";
HtmlTagsAndAttributes htmlAttributes = HtmlTagsAndAttributes.getInstance();
SimpleStringBag excluded = new SimpleStringBag();
assertEquals(1, htmlAttributes.searchAttributes("html", excluded, sampleAttribute).size());
excluded.add(sampleAttribute);
JsonArray<Token> tokens1 = JsonCollections.createArray();
tokens1.add(new Token(CodeMirror2.HTML, TokenType.TAG, "<html"));
tokens1.add(new Token(CodeMirror2.HTML, TokenType.WHITESPACE, " "));
String line1 = tokensToText(tokens1);
parsedLines.put(line1, tokens1);
JsonArray<Token> tokens2 = JsonCollections.createArray();
tokens2.add(new Token(CodeMirror2.HTML, TokenType.ATTRIBUTE, sampleAttribute));
tokens2.add(new Token(CodeMirror2.HTML, TokenType.TAG, ">"));
String line2 = tokensToText(tokens2);
parsedLines.put(line2, tokens2);
String text = line1 + "\n" + line2 + "\n";
helper.setup(path, text, 0, line1.length(), false);
helper.parser.getListenerRegistrar().add(new DocumentParserListenerAdapter(
helper.autocompleter, helper.editor));
helper.parser.begin();
parseOneLine();
helper.autocompleter.requestAutocomplete();
// "...please wait..."
assertEquals(1, helper.popup.proposals.size());
parseOneLine();
assertEquals(htmlAttributes.searchAttributes("html", excluded, "").size(),
helper.popup.proposals.size());
}
/**
* Tests that {@link HtmlAutocompleter#getModeForColumn} gets the mode from
* the anchor set prior to the given column.
*/
public void testGetModeForColumn() {
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.TAG, "<html"));
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.WHITESPACE, " "));
prepareAutocompleter();
Document document = helper.editor.getDocument();
AnchorManager anchorManager = document.getAnchorManager();
Line line = document.getFirstLine();
HtmlAutocompleter htmlAutocompleter = helper.autocompleter.htmlAutocompleter;
// Delete the anchor that was set by prepareAutocompleter().
JsonArray<Anchor> anchors =
AnchorManager.getAnchorsByTypeOrNull(line, HtmlAutocompleter.MODE_ANCHOR_TYPE);
assertEquals(0, anchors.size());
assertEquals(CodeMirror2.HTML, htmlAutocompleter.getModeForColumn(line, 0));
assertEquals(CodeMirror2.HTML, htmlAutocompleter.getModeForColumn(line, 1));
assertEquals(CodeMirror2.HTML, htmlAutocompleter.getModeForColumn(line, 2));
assertEquals(CodeMirror2.HTML, htmlAutocompleter.getModeForColumn(line, 3));
Anchor anchor = anchorManager.createAnchor(HtmlAutocompleter.MODE_ANCHOR_TYPE, line,
AnchorManager.IGNORE_LINE_NUMBER, 2);
anchor.setValue("m1");
assertEquals(CodeMirror2.HTML, htmlAutocompleter.getModeForColumn(line, 0));
assertEquals(CodeMirror2.HTML, htmlAutocompleter.getModeForColumn(line, 1));
assertEquals(CodeMirror2.HTML, htmlAutocompleter.getModeForColumn(line, 2));
assertEquals("m1", htmlAutocompleter.getModeForColumn(line, 3));
assertEquals("m1", htmlAutocompleter.getModeForColumn(line, 4));
assertEquals("m1", htmlAutocompleter.getModeForColumn(line, 5));
assertEquals("m1", htmlAutocompleter.getModeForColumn(line, 6));
assertEquals("m1", htmlAutocompleter.getModeForColumn(line, 10));
anchor = anchorManager.createAnchor(HtmlAutocompleter.MODE_ANCHOR_TYPE, line,
AnchorManager.IGNORE_LINE_NUMBER, 1);
anchor.setValue("m0");
assertEquals(CodeMirror2.HTML, htmlAutocompleter.getModeForColumn(line, 0));
assertEquals(CodeMirror2.HTML, htmlAutocompleter.getModeForColumn(line, 1));
assertEquals("m0", htmlAutocompleter.getModeForColumn(line, 2));
assertEquals("m1", htmlAutocompleter.getModeForColumn(line, 3));
assertEquals("m1", htmlAutocompleter.getModeForColumn(line, 4));
assertEquals("m1", htmlAutocompleter.getModeForColumn(line, 5));
assertEquals("m1", htmlAutocompleter.getModeForColumn(line, 6));
assertEquals("m1", htmlAutocompleter.getModeForColumn(line, 10));
anchor = anchorManager.createAnchor(HtmlAutocompleter.MODE_ANCHOR_TYPE, line,
AnchorManager.IGNORE_LINE_NUMBER, 5);
anchor.setValue("m2");
assertEquals(CodeMirror2.HTML, htmlAutocompleter.getModeForColumn(line, 0));
assertEquals(CodeMirror2.HTML, htmlAutocompleter.getModeForColumn(line, 1));
assertEquals("m0", htmlAutocompleter.getModeForColumn(line, 2));
assertEquals("m1", htmlAutocompleter.getModeForColumn(line, 3));
assertEquals("m1", htmlAutocompleter.getModeForColumn(line, 4));
assertEquals("m1", htmlAutocompleter.getModeForColumn(line, 5));
assertEquals("m2", htmlAutocompleter.getModeForColumn(line, 6));
assertEquals("m2", htmlAutocompleter.getModeForColumn(line, 10));
anchor = anchorManager.createAnchor(HtmlAutocompleter.MODE_ANCHOR_TYPE, line,
AnchorManager.IGNORE_LINE_NUMBER, 4);
anchor.setValue("m3");
assertEquals(CodeMirror2.HTML, htmlAutocompleter.getModeForColumn(line, 0));
assertEquals(CodeMirror2.HTML, htmlAutocompleter.getModeForColumn(line, 1));
assertEquals("m0", htmlAutocompleter.getModeForColumn(line, 2));
assertEquals("m1", htmlAutocompleter.getModeForColumn(line, 3));
assertEquals("m1", htmlAutocompleter.getModeForColumn(line, 4));
assertEquals("m3", htmlAutocompleter.getModeForColumn(line, 5));
assertEquals("m2", htmlAutocompleter.getModeForColumn(line, 6));
assertEquals("m2", htmlAutocompleter.getModeForColumn(line, 10));
}
/**
* Tests {@link HtmlAutocompleter#putModeAnchors}.
*/
public void testPutModeAnchors() {
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.TAG, "<html"));
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.WHITESPACE, " "));
prepareAutocompleter();
Document document = helper.editor.getDocument();
Line line = document.getFirstLine();
HtmlAutocompleter htmlAutocompleter = helper.autocompleter.htmlAutocompleter;
// Delete the anchor that was set by prepareAutocompleter().
JsonArray<Anchor> anchors =
AnchorManager.getAnchorsByTypeOrNull(line, HtmlAutocompleter.MODE_ANCHOR_TYPE);
assertEquals(0, anchors.size());
// Modes are empty, the previous line is null.
JsonArray<Pair<Integer, String>> modes = JsonCollections.createArray();
htmlAutocompleter.putModeAnchors(line, modes);
assertTrue(
AnchorManager.getAnchorsByTypeOrNull(line, HtmlAutocompleter.MODE_ANCHOR_TYPE).isEmpty());
// Modes are empty, the previous line is "null object".
htmlAutocompleter.putModeAnchors(line, modes);
assertTrue(
AnchorManager.getAnchorsByTypeOrNull(line, HtmlAutocompleter.MODE_ANCHOR_TYPE).isEmpty());
// Create a line in another document and use it as a not-null previousLine.
Line previousLine = Document.createFromString("").getFirstLine();
assertNull(
AnchorManager.getAnchorsByTypeOrNull(previousLine, HtmlAutocompleter.MODE_ANCHOR_TYPE));
// Modes are empty, the previous line has no mode anchor.
htmlAutocompleter.putModeAnchors(line, modes);
assertTrue(
AnchorManager.getAnchorsByTypeOrNull(line, HtmlAutocompleter.MODE_ANCHOR_TYPE).isEmpty());
// Modes are empty, the previous line has mode anchor.
Anchor previousLineAnchor = previousLine.getDocument().getAnchorManager().createAnchor(
HtmlAutocompleter.MODE_ANCHOR_TYPE, previousLine, AnchorManager.IGNORE_LINE_NUMBER, 0);
previousLineAnchor.setValue("m1");
htmlAutocompleter.putModeAnchors(line, modes);
assertTrue(modes.isEmpty());
anchors = AnchorManager.getAnchorsByTypeOrNull(line, HtmlAutocompleter.MODE_ANCHOR_TYPE);
assertEquals(0, anchors.size());
// Modes are not empty (one mode), the previous line has mode anchor.
modes.add(new Pair<Integer, String>(0, "m2"));
htmlAutocompleter.putModeAnchors(line, modes);
anchors = AnchorManager.getAnchorsByTypeOrNull(line, HtmlAutocompleter.MODE_ANCHOR_TYPE);
assertEquals(1, anchors.size());
assertEquals(0, anchors.get(0).getColumn());
assertEquals("m2", anchors.get(0).getValue());
// Modes are not empty (two modes), the previous line has mode anchor.
modes.add(new Pair<Integer, String>(3, "m3"));
htmlAutocompleter.putModeAnchors(line, modes);
anchors = AnchorManager.getAnchorsByTypeOrNull(line, HtmlAutocompleter.MODE_ANCHOR_TYPE);
assertEquals(2, anchors.size());
assertEquals(0, anchors.get(0).getColumn());
assertEquals("m2", anchors.get(0).getValue());
assertEquals(3, anchors.get(1).getColumn());
assertEquals("m3", anchors.get(1).getValue());
// Modes are not empty (two modes), the previous line is null.
htmlAutocompleter.putModeAnchors(line, modes);
anchors = AnchorManager.getAnchorsByTypeOrNull(line, HtmlAutocompleter.MODE_ANCHOR_TYPE);
assertEquals(2, anchors.size());
assertEquals(0, anchors.get(0).getColumn());
assertEquals("m2", anchors.get(0).getValue());
assertEquals(3, anchors.get(1).getColumn());
assertEquals("m3", anchors.get(1).getValue());
}
/**
* Tests that proposal list is updated when parsing of document is finished.
*/
public void testUpdateOnDocFinish() {
String sampleAttribute = "accesskey";
HtmlTagsAndAttributes htmlAttributes = HtmlTagsAndAttributes.getInstance();
SimpleStringBag excluded = new SimpleStringBag();
assertEquals(1, htmlAttributes.searchAttributes("html", excluded, sampleAttribute).size());
excluded.add(sampleAttribute);
JsonArray<Token> tokens1 = JsonCollections.createArray();
tokens1.add(new Token(CodeMirror2.HTML, TokenType.TAG, "<html"));
tokens1.add(new Token(CodeMirror2.HTML, TokenType.WHITESPACE, " "));
String line1 = tokensToText(tokens1);
parsedLines.put(line1, tokens1);
JsonArray<Token> tokens2 = JsonCollections.createArray();
tokens2.add(new Token(CodeMirror2.HTML, TokenType.ATTRIBUTE, sampleAttribute));
String line2 = tokensToText(tokens2);
parsedLines.put(line2, tokens2);
JsonArray<Token> tokens3 = JsonCollections.createArray();
tokens3.add(new Token(CodeMirror2.HTML, TokenType.WHITESPACE, " "));
String line3 = tokensToText(tokens3);
parsedLines.put(line3, tokens3);
String text = line1 + "\n" + line2 + "\n ";
helper.setup(path, text, 0, line1.length(), false);
helper.parser.getListenerRegistrar().add(new DocumentParserListenerAdapter(
helper.autocompleter, helper.editor));
helper.parser.begin();
parseOneLine();
helper.autocompleter.requestAutocomplete();
// "...please wait..."
assertEquals(1, helper.popup.proposals.size());
parseOneLine();
// Still "...please wait..."
assertEquals(1, helper.popup.proposals.size());
parseOneLine();
assertEquals(htmlAttributes.searchAttributes("html", excluded, "").size(),
helper.popup.proposals.size());
}
/**
* Tests that find autocompletions do not ruin existing "clean" results.
*/
public void testFindDoNotRuinResults() {
String id = "id";
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.TAG, "<html"));
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.WHITESPACE, " "));
prepareAutocompleter();
HtmlTagWithAttributes before = helper.editor.getDocument().getFirstLine().getTag(
XmlCodeAnalyzer.TAG_END_TAG);
assertNotNull(before);
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.ATTRIBUTE, id));
helper.autocompleter.htmlAutocompleter.findAutocompletions(
helper.editor.getSelection(), CTRL_SPACE);
HtmlTagWithAttributes after = helper.editor.getDocument().getFirstLine().getTag(
XmlCodeAnalyzer.TAG_END_TAG);
assertTrue("reference equality", before == after);
assertFalse("tag modifications", after.getAttributes().contains(id));
}
/**
* Tests that used attributes are excluded.
*/
public void testExcludedHtmlAttributes() {
String reversed = "reversed";
HtmlTagsAndAttributes htmlAttributes = HtmlTagsAndAttributes.getInstance();
SimpleStringBag excluded = new SimpleStringBag();
assertEquals(1, htmlAttributes.searchAttributes("ol", excluded, reversed).size());
excluded.add(reversed);
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.TAG, "<ol"));
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.WHITESPACE, " "));
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.ATTRIBUTE, reversed));
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.ATOM, "="));
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.STRING, "\"\""));
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.WHITESPACE, " "));
AutocompleteProposals proposals = findAutocompletions();
assertEquals(htmlAttributes.searchAttributes("ol", excluded, "").size(), proposals.size());
assertEquals("accesskey", proposals.get(0).getName());
assertNull(TestUtils.selectProposalByName(proposals, reversed));
}
/**
* Tests the proposals for ELEMENT.
*/
public void testAutocompleteHtmlElements() {
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.TAG, "<a"));
AutocompleteProposals proposals = findAutocompletions();
assertEquals(7, proposals.size());
assertEquals("abbr", proposals.get(1).getName());
lineTokens.clear();
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.TAG, "<bod"));
proposals = findAutocompletions();
assertEquals(1, proposals.size());
assertEquals("body", proposals.get(0).getName());
lineTokens.clear();
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.TAG, "<body"));
assertEquals(1, findAutocompletions().size());
lineTokens.clear();
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.TAG, "<body"));
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.TAG, ">"));
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.TAG, "</body"));
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.TAG, ">"));
assertTrue(findAutocompletions().isEmpty());
lineTokens.clear();
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.TAG, "<"));
assertEquals(
HtmlTagsAndAttributes.getInstance().searchTags("").size(), findAutocompletions().size());
}
/**
* Tests the autocompletion of self-closing tag.
*/
public void testAutocompleteSelfClosingTag() {
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.TAG, "<lin"));
AutocompleteProposals autocompletions = findAutocompletions();
assertNotNull(autocompletions);
ProposalWithContext linkProposal = TestUtils.selectProposalByName(autocompletions, "link");
assertNotNull(linkProposal);
AutocompleteResult commonResult =
helper.autocompleter.htmlAutocompleter.computeAutocompletionResult(linkProposal);
assertTrue("result type", commonResult instanceof DefaultAutocompleteResult);
DefaultAutocompleteResult result = (DefaultAutocompleteResult) commonResult;
assertEquals(4, result.getJumpLength());
assertEquals("link />", result.getAutocompletionText());
}
/**
* Tests full autocompletion for ATTRIBUTE.
*/
public void testJumpLengthAndFullAutocompletionHtmlAttribute() {
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.TAG, "<body"));
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.WHITESPACE, " "));
AutocompleteProposals autocompletions = findAutocompletions();
assertNotNull(autocompletions);
ProposalWithContext onloadProposal = TestUtils.selectProposalByName(autocompletions, "onload");
assertNotNull(onloadProposal);
AutocompleteResult commonResult =
helper.autocompleter.htmlAutocompleter.computeAutocompletionResult(onloadProposal);
assertTrue("result type", commonResult instanceof DefaultAutocompleteResult);
DefaultAutocompleteResult result = (DefaultAutocompleteResult) commonResult;
assertEquals(8, result.getJumpLength());
String fullAutocompletion = "onload=\"\"";
assertEquals(fullAutocompletion, result.getAutocompletionText());
}
/**
* Tests full autocompletion for ELEMENT.
*/
public void testJumpLengthAndFullAutocompletionHtmlElement() {
lineTokens.add(new Token(CodeMirror2.HTML, TokenType.TAG, "<bod"));
AutocompleteProposals autocompletions = findAutocompletions();
assertNotNull(autocompletions);
ProposalWithContext bodyProposal = TestUtils.selectProposalByName(autocompletions, "body");
assertNotNull(bodyProposal);
AutocompleteResult commonResult =
helper.autocompleter.htmlAutocompleter.computeAutocompletionResult(bodyProposal);
assertTrue("result type", commonResult instanceof DefaultAutocompleteResult);
DefaultAutocompleteResult result = (DefaultAutocompleteResult) commonResult;
assertEquals(5, result.getJumpLength());
String fullAutocompletion = "body></body>";
assertEquals(fullAutocompletion, result.getAutocompletionText());
}
}