/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.wave.api;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.common.collect.Lists;
import com.google.wave.api.Function.BlipContentFunction;
import com.google.wave.api.Function.MapFunction;
import com.google.wave.api.JsonRpcConstant.ParamsProperty;
import com.google.wave.api.impl.DocumentModifyAction;
import com.google.wave.api.impl.DocumentModifyAction.BundledAnnotation;
import junit.framework.TestCase;
import org.waveprotocol.wave.model.id.WaveId;
import org.waveprotocol.wave.model.id.WaveletId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* Test cases for {@link Blip}.
*/
public class BlipRobotTest extends TestCase {
private static final String ANNOTATION_KEY = "style/fontWeight";
private static final String ROOT_BLIP_ID = "b+43";
private static final String CHILD_BLIP_ID = "b+44";
private final Map<String, Blip> blips = new HashMap<String, Blip>();
private Wavelet wavelet;
@Override
protected void setUp() throws Exception {
wavelet = mock(Wavelet.class);
when(wavelet.getBlips()).thenReturn(blips);
when(wavelet.getOperationQueue()).thenReturn(new OperationQueue());
when(wavelet.getWaveId()).thenReturn(WaveId.of("example.com", "wave1"));
when(wavelet.getWaveletId()).thenReturn(WaveletId.of("example.com", "wavelet1"));
when(wavelet.getThread(anyString())).thenReturn(new BlipThread("rootThread", -1,
Lists.newArrayList(ROOT_BLIP_ID, CHILD_BLIP_ID), blips));
}
private Blip newBlip(String content, List<Annotation> annotations) {
return newBlip(ROOT_BLIP_ID, Arrays.asList(CHILD_BLIP_ID), null, annotations, content);
}
private Blip newBlip(String blipId, List<String> childBlipIds, String parentBlipId) {
return newBlip(blipId, childBlipIds, parentBlipId,
Arrays.asList(new Annotation("key", "val", 2, 3)));
}
private Blip newBlip(String blipId, List<String> childBlipIds, String parentBlipId,
List<Annotation> annotations) {
return newBlip(blipId, childBlipIds, parentBlipId, annotations,
"\nhello world!\n another line");
}
private Blip newBlip(String blipId, List<String> childBlipIds, String parentBlipId,
List<Annotation> annotations, String content) {
SortedMap<Integer, Element> elements = new TreeMap<Integer, Element>();
elements.put(14, new Gadget("http://a/b.xml"));
Blip blip = new Blip(blipId, childBlipIds, content,
Arrays.asList("robot@test.com", "user@test.com"), "user@test.com", 1000l, 123l,
parentBlipId, null, annotations, elements, new ArrayList<String>(), wavelet);
blips.put(blipId, blip);
return blip;
}
public void testDocumentOperations() throws Exception {
Blip blip = newBlip(ROOT_BLIP_ID, Arrays.asList(CHILD_BLIP_ID), null);
List<BlipContent> newLines = blip.all("\n").values();
assertEquals(2, newLines.size());
assertEquals(Arrays.asList(Plaintext.of("\n"), Plaintext.of("\n")), newLines);
blip.first("world").replace("jupiter");
String[] bits = blip.getContent().split("\n");
assertEquals(3, bits.length);
assertEquals("hello jupiter!", bits[1]);
blip.range(2, 5).delete();
assertTrue(blip.getContent().startsWith("\nho jupiter!"));
blip.first("ho").insertAfter("la");
assertTrue(blip.getContent().startsWith("\nhola jupiter!"));
blip.at(3).insert(" ");
assertTrue(blip.getContent().startsWith("\nho la jupiter!"));
blip.all().delete();
blip.at(1).insert("world!");
blip.first("world").insert(new BlipContentFunction() {
@Override
public BlipContent call(BlipContent source) {
return Plaintext.of("Hello " + source.getText().length() + " ");
}
});
assertEquals("\nHello 5 world!", blip.getContent());
}
public void testElementHandling() throws Exception {
Blip blip = newBlip(ROOT_BLIP_ID, Arrays.asList(CHILD_BLIP_ID), null);
int originalLength = blip.getContent().length();
String url = "http://www.test.com/image.png";
blip.append(new Image(url, 100, 100, "Cool pix."));
assertEquals(2, blip.getElements().size());
List<BlipContent> result = blip.all(ElementType.IMAGE, Image.restrictByUrl(url)).values();
assertEquals(1, result.size());
Element element = result.get(0).asElement();
assertTrue(element instanceof Image);
blip.at(1).insert("twelve chars");
assertTrue(blip.getContent().startsWith("\ntwelve charshello"));
element = blip.at(originalLength + 12).value().asElement();
assertTrue(element instanceof Image);
blip.first("twelve ").delete();
assertTrue(blip.getContent().startsWith("\nchars"));
element = blip.at(originalLength + 12 - "twelve ".length()).value().asElement();
assertTrue(element instanceof Image);
blip.first("chars").replace(new Image(url, 200, 200, "Yet another cool pix."));
assertEquals(3, blip.getElements().size());
assertTrue(blip.getContent().startsWith("\n hello"));
element = blip.at(1).value().asElement();
assertTrue(element instanceof Image);
assertEquals("Yet another cool pix.", ((Image) element).getCaption());
blip.all().delete();
blip.append(new Image(url, 100, 100, "Cool pix."));
blip.append(" some piece of text.");
assertEquals("\n some piece of text.", blip.getContent());
assertEquals(url, ((Image) blip.first(ElementType.IMAGE).value()).getUrl());
blip.first(ElementType.IMAGE).insertAfter(new BlipContentFunction() {
@Override
public BlipContent call(BlipContent source) {
Image matchedImage = (Image) source;
return Plaintext.of(matchedImage.getUrl());
}
});
assertEquals("\n " + url + " some piece of text.", blip.getContent());
blip.all().delete();
blip.append(new Image(url, 100, 100, "Cool pix."));
blip.append(" some piece of text.");
assertEquals("\n some piece of text.", blip.getContent());
assertEquals(url, ((Image) blip.first(ElementType.IMAGE).value()).getUrl());
blip.first(ElementType.IMAGE).replace(new BlipContentFunction() {
@Override
public BlipContent call(BlipContent source) {
Image matchedImage = (Image) source;
return new Image(matchedImage.getUrl() + "?query=foo", matchedImage.getWidth(),
matchedImage.getHeight(), matchedImage.getCaption());
}
});
assertEquals(url + "?query=foo", ((Image) blip.first(ElementType.IMAGE).value()).getUrl());
}
public void testUpdateElement() throws Exception {
Blip blip = newBlip(ROOT_BLIP_ID, Arrays.asList(CHILD_BLIP_ID), null);
String url = "http://www.test.com/image.png";
blip.append(new Image(url, 100, 100, "Cool pix."));
assertEquals(2, blip.getElements().size());
// Update the image by appending a query param to the URL.
blip.first(ElementType.IMAGE).updateElement(new MapFunction() {
@Override
public Map<String, String> call(BlipContent source) {
Image matchedImage = (Image) source;
Map<String, String> properties = new HashMap<String, String>();
properties.put("url", matchedImage.getUrl() + "?version=newversion");
return properties;
}
});
Image image = (Image) blip.first(ElementType.IMAGE).value();
assertEquals(url + "?version=newversion", image.getUrl());
}
public void testAnnotationHandling() throws Exception {
String key = Annotation.FONT_WEIGHT;
Blip blip = newBlip(ROOT_BLIP_ID, Arrays.asList(CHILD_BLIP_ID), null,
Arrays.asList(new Annotation(key, "bold", 3, 6)));
assertEquals(1, blip.getAnnotations().size());
assertEquals(new Annotation(key, "bold", 3, 6), blip.getAnnotations().get(key).get(0));
// Extend the bold annotation.
blip.range(5, 8).annotate(key, "bold");
assertEquals(1, blip.getAnnotations().size());
assertEquals(new Annotation(key, "bold", 3, 8), blip.getAnnotations().get(key).get(0));
// Clip by adding another annotation with the same key.
blip.range(4, 12).annotate(key, "italic");
assertEquals(1, blip.getAnnotations().size());
assertEquals(2, blip.getAnnotations().get(key).size());
assertEquals(new Annotation(key, "bold", 3, 4), blip.getAnnotations().get(key).get(0));
assertEquals(new Annotation(key, "italic", 4, 12), blip.getAnnotations().get(key).get(1));
// Split the italic annotation.
blip.range(6, 7).clearAnnotation(key);
assertEquals(3, blip.getAnnotations().get(key).size());
assertEquals(new Annotation(key, "bold", 3, 4), blip.getAnnotations().get(key).get(0));
assertEquals(new Annotation(key, "italic", 4, 6), blip.getAnnotations().get(key).get(1));
assertEquals(new Annotation(key, "italic", 7, 12), blip.getAnnotations().get(key).get(2));
// Test names and iteration.
assertEquals(1, blip.getAnnotations().namesSet().size());
Annotation[] expected = {
new Annotation(key, "bold", 3, 4),
new Annotation(key, "italic", 4, 6),
new Annotation(key, "italic", 7, 12)
};
int index = 0;
for (Annotation annotation : blip.getAnnotations()) {
assertEquals(expected[index++], annotation);
}
assertEquals(3, index);
blip.range(3, 5).annotate("foo", "bar");
assertEquals(2, blip.getAnnotations().namesSet().size());
assertEquals(3, blip.getAnnotations().get(key).size());
assertEquals(1, blip.getAnnotations().get("foo").size());
blip.range(3, 5).clearAnnotation("foo");
// Clear the whole thing.
blip.all().clearAnnotation(key);
assertNull(blip.getAnnotations().get(key));
}
public void testBlipOperations() throws Exception {
Blip blip = newBlip(ROOT_BLIP_ID, Arrays.asList(CHILD_BLIP_ID), null);
assertEquals(1, wavelet.getBlips().size());
Blip other = blip.reply();
other.append("hello world");
assertEquals("\nhello world", other.getContent());
assertEquals(2, wavelet.getBlips().size());
Blip inline = blip.insertInlineBlip(3);
assertEquals("\n", inline.getContent());
assertEquals(3, wavelet.getBlips().size());
}
public void testInsertInlineBlipCantInsertAtTheBeginning() throws Exception {
Blip blip = newBlip(ROOT_BLIP_ID, Arrays.asList(CHILD_BLIP_ID), null);
assertEquals(1, wavelet.getBlips().size());
try {
blip.insertInlineBlip(0);
fail("Should have thrown an exception when trying to insert inline blip at index 0.");
} catch (IllegalArgumentException e) {
// Expected.
}
assertEquals(1, wavelet.getBlips().size());
}
public void testDocumentModify() throws Exception {
Blip blip = newBlip(ROOT_BLIP_ID, Arrays.asList(CHILD_BLIP_ID), null);
blip.all().replace("a text with text and then some text");
assertEquals("\na text with text and then some text", blip.getContent());
blip.at(8).insert("text ");
blip.all("text").replace("foo", "bar");
assertEquals("\na foo bar with foo and then some bar", blip.getContent());
}
public void testBundledAnnotations() throws Exception {
Blip blip = newBlip(ROOT_BLIP_ID, Arrays.asList(CHILD_BLIP_ID), null);
blip.all().insert(BundledAnnotation.listOf("style", "bold"), "\nhello");
assertEquals(2, blip.getAnnotations().size());
assertEquals(new Annotation("style", "bold", 0, 6), blip.getAnnotations().get("style").get(0));
}
public void testProxyFor() throws Exception {
Blip blip = newBlip(ROOT_BLIP_ID, Arrays.asList(CHILD_BLIP_ID), null);
Blip proxiedBlip = blip.proxyFor("proxyuser");
assertEquals(blip.getBlipId(), proxiedBlip.getBlipId());
assertEquals(blip.getWaveId(), proxiedBlip.getWaveId());
assertEquals(blip.getWaveletId(), proxiedBlip.getWaveletId());
proxiedBlip.reply();
List<OperationRequest> pendingOps = proxiedBlip.getOperationQueue().getPendingOperations();
assertEquals(1, pendingOps.size());
assertEquals("proxyuser", pendingOps.get(0).getParameter(ParamsProperty.PROXYING_FOR));
}
public void testProxyForShouldFailIfProxyIdIsInvalid() throws Exception {
try {
newBlip(ROOT_BLIP_ID, Arrays.asList(CHILD_BLIP_ID), null).proxyFor("foo@bar.com");
fail("Should have failed since proxy id is not encoded.");
} catch (IllegalArgumentException e) {
// Expected.
}
}
@SuppressWarnings("unchecked")
public void testDocumentModifyParametersForUpdateElement() {
Blip blip = newBlip(ROOT_BLIP_ID, Arrays.asList(CHILD_BLIP_ID), null);
Map<String, String> newProperties = new HashMap<String, String>();
newProperties.put("url", "http://www.google.com/gadget.xml");
blip.first(ElementType.GADGET).updateElement(newProperties);
List<OperationRequest> ops = blip.getOperationQueue().getPendingOperations();
DocumentModifyAction action = (DocumentModifyAction) ops.get(0).getParameter(
ParamsProperty.MODIFY_ACTION);
assertEquals("http://www.google.com/gadget.xml", action.getElement(0).getProperty("url"));
}
public void testDocumentModifyParametersForAnnotate() {
Blip blip = newBlip(ROOT_BLIP_ID, Arrays.asList(CHILD_BLIP_ID), null);
blip.all().replace("foo foo foo");
blip.all("foo").annotate("key", "value1", "value2", "value3");
List<OperationRequest> ops = blip.getOperationQueue().getPendingOperations();
DocumentModifyAction action = (DocumentModifyAction) ops.get(ops.size() - 1).getParameter(
ParamsProperty.MODIFY_ACTION);
assertEquals("key", action.getAnnotationKey());
assertEquals(Arrays.asList("value1", "value2", "value3"), action.getValues());
}
public void testDocumentModifyParametersForClearAnnotation() {
Blip blip = newBlip(ROOT_BLIP_ID, Arrays.asList(CHILD_BLIP_ID), null);
blip.all().clearAnnotation("key");
List<OperationRequest> ops = blip.getOperationQueue().getPendingOperations();
DocumentModifyAction action = (DocumentModifyAction) ops.get(ops.size() - 1).getParameter(
ParamsProperty.MODIFY_ACTION);
assertEquals("key", action.getAnnotationKey());
}
public void testDocumentModifyParametersForInsertInsertAfterAndReplace() throws Exception {
Blip blip = newBlip(ROOT_BLIP_ID, Arrays.asList(CHILD_BLIP_ID), null);
blip.at(0).insert(new Image("http://a/b.gif", 100, 100, "Foo"), Plaintext.of("bold"),
Plaintext.of("text"));
List<OperationRequest> ops = blip.getOperationQueue().getPendingOperations();
DocumentModifyAction action = (DocumentModifyAction) ops.get(ops.size() - 1).getParameter(
ParamsProperty.MODIFY_ACTION);
assertEquals(3, action.getValues().size());
assertFalse(action.hasTextAt(0));
assertNull(action.getValues().get(0));
assertTrue(action.hasTextAt(1));
assertEquals("bold", action.getValues().get(1));
assertTrue(action.hasTextAt(2));
assertEquals("text", action.getValues().get(2));
assertEquals(3, action.getElements().size());
Element el = action.getElements().get(0);
assertTrue(el instanceof Image);
assertEquals("http://a/b.gif", ((Image) el).getUrl());
assertNull(action.getElements().get(1));
assertNull(action.getElements().get(2));
}
public void testDeleteRangeThatSpansAcrossAnnotationEndPoint() throws Exception {
Blip blip = newBlip("\nFoo bar.", Arrays.asList(new Annotation(ANNOTATION_KEY, "bold", 1, 3)));
// Delete "oo"
blip.range(2, 4).delete();
assertEquals("\nF bar.", blip.getContent());
assertEquals(new Range(1, 2), blip.getAnnotations().get(ANNOTATION_KEY).get(0).getRange());
}
public void testInsertBeforeAnnotationStartPoint() {
Blip blip = newBlip("\nFoo bar.", Arrays.asList(new Annotation(ANNOTATION_KEY, "bold", 4, 9)));
blip.at(4).insert("d and");
assertEquals(new Range(9, 14), blip.getAnnotations().get(ANNOTATION_KEY).get(0).getRange());
}
public void testDeleteRangeInsideAnnotation() {
Blip blip = newBlip("\nFoo bar.", Arrays.asList(new Annotation(ANNOTATION_KEY, "bold", 1, 5)));
// Delete "oo"
blip.range(2, 4).delete();
assertEquals("\nF bar.", blip.getContent());
assertEquals(new Range(1, 3), blip.getAnnotations().get(ANNOTATION_KEY).get(0).getRange());
}
public void testReplaceInsideAnnotation() {
Blip blip = newBlip("\nFoo bar.", Arrays.asList(new Annotation(ANNOTATION_KEY, "bold", 1, 5)));
// Replace "oo" with "ooo".
blip.range(2, 4).replace("ooo");
assertEquals("\nFooo bar.", blip.getContent());
assertEquals(new Range(1, 6), blip.getAnnotations().get(ANNOTATION_KEY).get(0).getRange());
// Replace "ooo" with "o".
blip.range(2, 5).replace("o");
assertEquals("\nFo bar.", blip.getContent());
assertEquals(new Range(1, 4), blip.getAnnotations().get(ANNOTATION_KEY).get(0).getRange());
}
public void testReplaceSpanAnnotation() {
Blip blip = newBlip("\nFoo bar.", Arrays.asList(new Annotation(ANNOTATION_KEY, "bold", 1, 4)));
// Replace "oo bar." with "".
blip.range(2, 9).replace("");
assertEquals("\nF", blip.getContent());
assertEquals(new Range(1, 2), blip.getAnnotations().get(ANNOTATION_KEY).get(0).getRange());
}
public void testDeleteChildBlipId() {
Blip blip = newBlip("\nFoo bar.", Collections.<Annotation>emptyList());
assertEquals(1, blip.getChildBlipIds().size());
blip.deleteChildBlipId(CHILD_BLIP_ID);
assertEquals(0, blip.getChildBlipIds().size());
}
public void testSearchWithNoMatchShouldNotGenerateOperation() {
Blip blip = newBlip("\nFoo bar.", Collections.<Annotation>emptyList());
blip.all(":(").replace(":)");
assertEquals(0, wavelet.getOperationQueue().getPendingOperations().size());
}
public void testDeleteAll() {
Blip blip = newBlip("\nNew title\nNew content", Arrays.asList(
new Annotation("style/fontWeight", "bold", new Range(10,11)),
new Annotation("conv/title", "", new Range(0,10))));
blip.all().delete();
assertEquals("\n", blip.getContent());
}
public void testSerializeAndDeserialize() throws Exception {
SortedMap<Integer, Element> elements = new TreeMap<Integer, Element>();
elements.put(14, new Gadget("http://a/b.xml"));
Blip expectedBlip = new Blip("blip1", Arrays.asList("blip2", "blip3"),
"\nhello world!\n another line", Arrays.asList("robot@test.com", "user@test.com"),
"user@test.com", 1000l, 123l, null, null, Arrays.asList(new Annotation("key", "val", 2, 3)),
elements, new ArrayList<String>(), wavelet);
Blip actualBlip = Blip.deserialize(wavelet.getOperationQueue(), wavelet,
expectedBlip.serialize());
assertEquals(expectedBlip.getWaveId(), actualBlip.getWaveId());
assertEquals(expectedBlip.getWaveletId(), actualBlip.getWaveletId());
assertEquals(expectedBlip.getBlipId(), actualBlip.getBlipId());
assertEquals(expectedBlip.getContent(), actualBlip.getContent());
assertEquals(expectedBlip.getCreator(), actualBlip.getCreator());
assertEquals(expectedBlip.getLastModifiedTime(), actualBlip.getLastModifiedTime());
assertEquals(expectedBlip.getParentBlipId(), actualBlip.getParentBlipId());
assertEquals(expectedBlip.getVersion(), actualBlip.getVersion());
assertEquals(expectedBlip.getContributors(), actualBlip.getContributors());
assertEquals(expectedBlip.getChildBlipIds(), actualBlip.getChildBlipIds());
assertEquals(expectedBlip.getElements().keySet(), actualBlip.getElements().keySet());
assertEquals(expectedBlip.getAnnotations().size(), actualBlip.getAnnotations().size());
}
public void testAppendMarkup() throws Exception {
Blip blip = newBlip("\nFoo", Collections.<Annotation>emptyList());
blip.appendMarkup("foo");
assertEquals(1, wavelet.getOperationQueue().getPendingOperations().size());
assertEquals("\nFoofoo", blip.getContent());
blip.appendMarkup("foo <b>bar</b>");
assertEquals(2, wavelet.getOperationQueue().getPendingOperations().size());
assertEquals("\nFoofoofoo bar", blip.getContent());
blip.appendMarkup("foo<br>bar");
assertEquals(3, wavelet.getOperationQueue().getPendingOperations().size());
assertEquals("\nFoofoofoo barfoo\nbar", blip.getContent());
blip.appendMarkup("foo<p indent=\"3\">bar</p>");
assertEquals(4, wavelet.getOperationQueue().getPendingOperations().size());
assertEquals("\nFoofoofoo barfoo\nbarfoo\nbar", blip.getContent());
}
public void testIteration() throws Exception {
Blip blip = newBlip("\naaa 012 aaa 345 aaa 322", Collections.<Annotation>emptyList());
Range[] expectedRanges = {new Range(1, 4), new Range(9, 12), new Range(17, 20)};
BlipContentRefs blipRefs = blip.all("aaa");
int index = 0;
for (Range range : blipRefs) {
assertEquals(expectedRanges[index++], range);
}
assertEquals(3, index);
// Now let's make sure that we can iterate again.
index = 0;
for (Range range : blipRefs) {
assertEquals(expectedRanges[index++], range);
}
assertEquals(3, index);
// Assert iteration with blip refs that has no match.
assertFalse(blip.all("invalid").iterator().hasNext());
}
public void testInlineBlip() throws Exception {
Blip blip = newBlip("\n1234", Collections.<Annotation>emptyList());
assertEquals(-1, blip.getInlineBlipOffset());
Blip inlineBlip = blip.insertInlineBlip(3);
assertTrue(blip.getChildBlipIds().contains(inlineBlip.getBlipId()));
assertEquals(3, inlineBlip.getInlineBlipOffset());
assertEquals("\n12 34", blip.getContent());
assertEquals(ElementType.INLINE_BLIP, blip.getElements().get(3).getType());
}
private static void assertEquals(Annotation one, Annotation two) {
assertEquals(one.getName(), two.getName());
assertEquals(one.getValue(), two.getValue());
assertEquals(one.getRange().getStart(), two.getRange().getStart());
assertEquals(one.getRange().getEnd(), two.getRange().getEnd());
}
}