/** * Copyright 2009 Google Inc. * * 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 org.waveprotocol.wave.client.editor.webdriver; import com.google.gwt.dom.client.Element; import org.waveprotocol.wave.client.editor.Editor; import org.waveprotocol.wave.client.editor.EditorContext; import org.waveprotocol.wave.client.editor.EditorTestingUtil; import org.waveprotocol.wave.client.editor.content.DiffHighlightingFilter; import org.waveprotocol.wave.model.document.AnnotationInterval; import org.waveprotocol.wave.model.document.MutableAnnotationSet; import org.waveprotocol.wave.model.document.operation.automaton.DocumentSchema; import org.waveprotocol.wave.model.document.util.DocProviders; import org.waveprotocol.wave.model.document.util.FocusedRange; import org.waveprotocol.wave.model.document.util.Range; import org.waveprotocol.wave.model.document.util.XmlStringBuilder; import org.waveprotocol.wave.model.util.CollectionUtils; import org.waveprotocol.wave.model.util.Preconditions; import org.waveprotocol.wave.model.util.ReadableStringSet; /** * Utility that adds hooks for attaching functionality to an editor which can be triggered * through webdriver JSNI calls. @see EditorJSNIHelpers for the bridge calls. * * Call both EditorJsniHelpers.nativeSetupWebDriverTestPins() * and EditorWebDriverUtil.setDocumentSchema() during initialisation to set everything up. * * @author patcoleman@google.com (Pat Coleman) */ public class EditorWebDriverUtil { /** * Name of backref from main div to Editor object. Used by * {@link #webdriverEditorGetContent(Element)} */ private static final String EDITOR_WEBDRIVER_PROPERTY = "__editor"; private static DocumentSchema documentSchema = null; /** * Must be called with a non-null schema before any call to * {@link #webdriverEditorSetContent(Element,String)}. */ public static void setDocumentSchema(DocumentSchema schema) { documentSchema = schema; } public static void register(Editor editor, Element container) { container.setPropertyObject(EDITOR_WEBDRIVER_PROPERTY, editor); } public static void unregister(Element container) { register(null, container); } /** * @param editorDiv * @return Editor owning given doc div */ private static Editor getByEditorDiv(Element editorDiv) { return (editorDiv == null) ? null : (Editor) editorDiv.getPropertyObject(EDITOR_WEBDRIVER_PROPERTY); } /** Utility that flushes an editor div before getting the selected range. */ private static Range getSelectionWithFlush(Element editorDiv) { Editor editor = getByEditorDiv(editorDiv); if (editor != null) { EditorTestingUtil.forceFlush(editor); return editor.getSelectionHelper().getOrderedSelectionRange(); } return null; } /** * @param editorDiv * @return content of editor owning doc div */ public static String webdriverEditorGetContent(Element editorDiv) { Editor editor = getByEditorDiv(editorDiv); if (editor != null) { // This must not be called synchronously in the same key event before // the dom is modified. EditorTestingUtil.forceFlush(editor); // NOTE(patcoleman): it seems empty strings get converted to null objects here by webdriver, // this code removes the prefix that avoids that problem. // TODO(patcoleman): investigate where this happens with Simon. String content = XmlStringBuilder.innerXml(editor.getPersistentDocument()).toString(); return content == null ? null : "_" + content; } else { return "Error in webdriverEditorGetContent"; } } /** * @param editorDiv * @return local annotations of the editor owning div */ public static String webdriverEditorGetLocalDiffAnnotations(Element editorDiv) { Editor editor = getByEditorDiv(editorDiv); if (editor == null) { return "Error in webdriverEditorGetContent"; } // This must not be called synchronously in the same key event before the dom is modified. EditorTestingUtil.forceFlush(editor); StringBuilder ans = new StringBuilder(""); ReadableStringSet keys = CollectionUtils.newStringSet( DiffHighlightingFilter.DIFF_INSERT_KEY, DiffHighlightingFilter.DIFF_DELETE_KEY); MutableAnnotationSet.Local annotations = editor.getContent().getLocalAnnotations(); annotations.annotationIntervals(0, annotations.size(), keys); for (AnnotationInterval<Object> interval : annotations.annotationIntervals(0, annotations.size(), keys)) { boolean isInsertion = interval.annotations() .getExisting(DiffHighlightingFilter.DIFF_INSERT_KEY) != null; boolean isDeletion = interval.annotations() .getExisting(DiffHighlightingFilter.DIFF_DELETE_KEY) != null; char symbol; if (isInsertion && isDeletion) { symbol = '*'; } else if (isInsertion) { symbol = '+'; } else if (isDeletion) { symbol = '-'; } else { symbol = '.'; } for (int j = 0; j < interval.length(); j++) { ans.append(symbol); } } return ans.toString(); } /** * @param editorDiv * @return start selection of editor owning doc div */ public static int webdriverEditorGetStartSelection(Element editorDiv) { Range range = getSelectionWithFlush(editorDiv); return range == null ? -1 : range.getStart(); } /** * Get the div of the editor's document (not the editor's div). * The editor must have a document, otherwise an exception will be thrown. * * @param editorDiv * @return div of editor's document */ public static Element webdriverEditorGetDocDiv(Element editorDiv) { Editor editor = getByEditorDiv(editorDiv); return editor.getDocumentHtmlElement(); } /** * @param editorDiv * @return end selection of editor owning doc div */ public static int webdriverEditorGetEndSelection(Element editorDiv) { Range range = getSelectionWithFlush(editorDiv); return range == null ? -1 : range.getEnd(); } /** * @param editorDiv editor * @param end end of selection range */ public static void webdriverEditorSetSelection(Element editorDiv, int start, int end) { EditorContext editor = getByEditorDiv(editorDiv); editor.getSelectionHelper().setSelectionRange(new FocusedRange(start, end)); } /** * @param editorDiv editor * @param content content to set */ public static void webdriverEditorSetContent(Element editorDiv, String content) { Editor editor = getByEditorDiv(editorDiv); if (editor != null) { Preconditions.checkNotNull(documentSchema, "documentSchema is not set"); editor.setContent(DocProviders.POJO.parse(content).asOperation(), documentSchema); } } /** * @param editorDiv * @return local content of editor owning doc div */ public static String webdriverEditorGetLocalContent(Element editorDiv) { Editor editor = getByEditorDiv(editorDiv); if (editor != null) { // This must not be called synchronously in the same key event before // the dom is modified. EditorTestingUtil.forceFlush(editor); return XmlStringBuilder.innerXml(editor.getContent().getFullContentView()).toString(); } else { return "Error in webdriverEditorGetLocalContent"; } } }