/** * Copyright 2010 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.box.server.robots.operations; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.wave.api.Element; import com.google.wave.api.Gadget; import com.google.wave.api.InvalidRequestException; import com.google.wave.api.OperationRequest; import com.google.wave.api.OperationType; import com.google.wave.api.Range; import com.google.wave.api.JsonRpcConstant.ParamsProperty; import com.google.wave.api.OperationRequest.Parameter; import com.google.wave.api.data.ApiView; import com.google.wave.api.data.ApiView.ElementInfo; import com.google.wave.api.impl.DocumentModifyAction; import com.google.wave.api.impl.DocumentModifyAction.BundledAnnotation; import com.google.wave.api.impl.DocumentModifyAction.ModifyHow; import org.waveprotocol.box.server.robots.RobotsTestBase; import org.waveprotocol.box.server.robots.testing.OperationServiceHelper; import org.waveprotocol.wave.client.gadget.GadgetXmlUtil; import org.waveprotocol.wave.model.conversation.ObservableConversationBlip; import org.waveprotocol.wave.model.document.Document; import org.waveprotocol.wave.model.document.util.LineContainers; import org.waveprotocol.wave.model.document.util.XmlStringBuilder; import java.util.Collections; import java.util.List; import java.util.Map; /** * Unit tests for {@link DocumentModifyService}. * * @author ljvderijk@google.com (Lennard de Rijk) */ public class DocumentModifyServiceTest extends RobotsTestBase { private static final String NO_ANNOTATION_KEY = null; private static final List<String> NO_VALUES = Collections.<String> emptyList(); private static final List<BundledAnnotation> NO_BUNDLED_ANNOTATIONS = Collections.emptyList(); private static final List<Element> NO_ELEMENTS = Collections.emptyList(); private static final String ANNOTATION_VALUE = "annotationValue"; private static final String ANNOTATION_KEY = "annotationKey"; private static final String INITIAL_CONTENT = "Hello world!"; private static final int CONTENT_START_TEXT = 1; private static final int CONTENT_START_XML = 3; private DocumentModifyService service; private OperationServiceHelper helper; private String rootBlipId; @Override protected void setUp() throws Exception { service = DocumentModifyService.create(); helper = new OperationServiceHelper(WAVELET_NAME, ALEX); ObservableConversationBlip rootBlip = getRootBlip(); rootBlipId = rootBlip.getId(); LineContainers.appendToLastLine( rootBlip.getContent(), XmlStringBuilder.createText(INITIAL_CONTENT)); } public void testFailOnMultipleWhereParams() throws Exception { OperationRequest operation = operationRequest(OperationType.DOCUMENT_MODIFY, rootBlipId, Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction()), Parameter.of(ParamsProperty.RANGE, new Range(0, 1)), Parameter.of(ParamsProperty.INDEX, 0)); try { service.execute(operation, helper.getContext(), ALEX); fail("Expected InvalidRequestException"); } catch (InvalidRequestException e) { // expected } } public void testAnnotate() throws Exception { OperationRequest operation = operationRequest(OperationType.DOCUMENT_MODIFY, rootBlipId, Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction(ModifyHow.ANNOTATE, Collections.singletonList(ANNOTATION_VALUE), ANNOTATION_KEY, NO_ELEMENTS, NO_BUNDLED_ANNOTATIONS, false)), Parameter.of(ParamsProperty.INDEX, CONTENT_START_TEXT)); service.execute(operation, helper.getContext(), ALEX); String annotation = getRootBlip().getContent().getAnnotation(CONTENT_START_XML, ANNOTATION_KEY); assertEquals("Expected the text to be annotated", ANNOTATION_VALUE, annotation); assertNull("Expected this text not to be annotated", getRootBlip().getContent().getAnnotation(CONTENT_START_XML + 1, ANNOTATION_KEY)); } public void testClearAnnotatation() throws Exception { Document doc = getRootBlip().getContent(); doc.setAnnotation(CONTENT_START_XML, CONTENT_START_XML + 1, ANNOTATION_KEY, ANNOTATION_VALUE); String annotation = getRootBlip().getContent().getAnnotation(CONTENT_START_XML, ANNOTATION_KEY); assertEquals("Expected the text to be annotated", ANNOTATION_VALUE, annotation); OperationRequest operation = operationRequest(OperationType.DOCUMENT_MODIFY, rootBlipId, Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction(ModifyHow.CLEAR_ANNOTATION, NO_VALUES, ANNOTATION_KEY, NO_ELEMENTS, NO_BUNDLED_ANNOTATIONS, false)), Parameter.of(ParamsProperty.INDEX, CONTENT_START_TEXT)); service.execute(operation, helper.getContext(), ALEX); assertNull("Expected this text not to be annotated", getRootBlip().getContent().getAnnotation(CONTENT_START_XML, ANNOTATION_KEY)); } public void testInsertGadget() throws Exception { String gadgetUrl = "http://wave-api.appspot.com/public/gadgets/areyouin/gadget.xml"; List<Element> elementsIn = Lists.newArrayListWithCapacity(1); Map<String,String> properties = Maps.newHashMap(); properties.put("url", gadgetUrl); properties.put("author", ALEX.getAddress()); elementsIn.add(new Gadget(properties)); OperationRequest operation = operationRequest(OperationType.DOCUMENT_MODIFY, rootBlipId, Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction(ModifyHow.INSERT, NO_VALUES, NO_ANNOTATION_KEY, elementsIn, NO_BUNDLED_ANNOTATIONS, false)), Parameter.of(ParamsProperty.INDEX, CONTENT_START_TEXT)); service.execute(operation, helper.getContext(), ALEX); Gadget gadget = null; List<ElementInfo> elementsOut = getApiView().getElements(); int size = 0; for (ElementInfo elementOut : elementsOut) { if (!elementOut.element.isGadget()) { continue; } else { size++; gadget = (Gadget)elementOut.element; } } assertEquals(1, size); assertEquals(gadgetUrl, gadget.getUrl()); assertEquals(ALEX.getAddress(), gadget.getAuthor()); } public void testDelete() throws Exception { OperationRequest operation = operationRequest(OperationType.DOCUMENT_MODIFY, rootBlipId, Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction(ModifyHow.DELETE, NO_VALUES, NO_ANNOTATION_KEY, NO_ELEMENTS, NO_BUNDLED_ANNOTATIONS, false)), Parameter.of(ParamsProperty.INDEX, CONTENT_START_TEXT)); service.execute(operation, helper.getContext(), ALEX); // Cut off the /n String after = getApiView().apiContents().substring(1); assertEquals("First character should be deleted", INITIAL_CONTENT.substring(1), after); } public void testInsert() throws Exception { String toInsert = "insertedText"; // Insert a new piece of annotated text before the current text. OperationRequest operation = operationRequest(OperationType.DOCUMENT_MODIFY, rootBlipId, Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction(ModifyHow.INSERT, Collections.singletonList(toInsert), NO_ANNOTATION_KEY, NO_ELEMENTS, BundledAnnotation.listOf(ANNOTATION_KEY, ANNOTATION_VALUE), false)), Parameter.of(ParamsProperty.INDEX, CONTENT_START_TEXT)); service.execute(operation, helper.getContext(), ALEX); // Cut off the /n String result = getApiView().apiContents().substring(1); assertEquals( "The result should start with the inserted text", toInsert + INITIAL_CONTENT, result); String annotation = getRootBlip().getContent().getAnnotation(CONTENT_START_XML, ANNOTATION_KEY); assertEquals("Expected the text to be annotated", ANNOTATION_VALUE, annotation); } public void testInsertAfter() throws Exception { String toInsert = "insertedText"; OperationRequest operation = operationRequest(OperationType.DOCUMENT_MODIFY, rootBlipId, Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction(ModifyHow.INSERT_AFTER, Collections.singletonList(toInsert), NO_ANNOTATION_KEY, NO_ELEMENTS, NO_BUNDLED_ANNOTATIONS, false)), Parameter.of(ParamsProperty.INDEX, CONTENT_START_TEXT)); service.execute(operation, helper.getContext(), ALEX); // Cut off the /n String after = getApiView().apiContents().substring(1); assertEquals("Content should be insterted after the first character", INITIAL_CONTENT.charAt(0) + toInsert + INITIAL_CONTENT.substring(1), after); } public void testReplace() throws Exception { String replacement = "replacedText"; OperationRequest operation = operationRequest(OperationType.DOCUMENT_MODIFY, rootBlipId, Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction(ModifyHow.REPLACE, Collections.singletonList(replacement), NO_ANNOTATION_KEY, NO_ELEMENTS, NO_BUNDLED_ANNOTATIONS, false)), Parameter.of(ParamsProperty.RANGE, new Range(CONTENT_START_TEXT, CONTENT_START_TEXT + INITIAL_CONTENT.length()))); service.execute(operation, helper.getContext(), ALEX); // Cut off the /n String after = getApiView().apiContents().substring(1); assertEquals("The entire text should be replaced", replacement, after); } private ApiView getApiView() throws InvalidRequestException { ApiView view = new ApiView( getRootBlip().getContent(), helper.getContext().openWavelet(WAVE_ID, WAVELET_ID, ALEX)); return view; } private ObservableConversationBlip getRootBlip() throws InvalidRequestException { ObservableConversationBlip rootBlip = helper.getContext().openConversation(WAVE_ID, WAVELET_ID, ALEX).getRoot().getRootThread() .getFirstBlip(); return rootBlip; } }