/** * 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.model.document.util; import junit.framework.TestCase; import org.waveprotocol.wave.model.document.MutableDocument; import org.waveprotocol.wave.model.document.operation.automaton.DocumentSchema; import org.waveprotocol.wave.model.document.operation.impl.AttributesImpl; import org.waveprotocol.wave.model.document.raw.impl.Element; import org.waveprotocol.wave.model.document.raw.impl.Node; import org.waveprotocol.wave.model.document.raw.impl.Text; import org.waveprotocol.wave.model.document.util.ContextProviders.TestDocumentContext; import org.waveprotocol.wave.model.document.util.LineContainers.RoundDirection; import org.waveprotocol.wave.model.document.util.LineContainers.Rounding; import org.waveprotocol.wave.model.schema.AbstractXmlSchemaConstraints; import java.util.Collections; import java.util.List; /** * @author danilatos@google.com (Daniel Danilatos) */ public class LineContainersTest extends TestCase { private final DocumentSchema SCHEMA = new AbstractXmlSchemaConstraints() { { addChildren(null, "body"); addChildren("body", "line", "input"); containsBlipText("body"); containsBlipText("input"); addRequiredInitial("body", Collections.singletonList("line")); addAttrWithValues("line", "t", "h1", "h2", "h3", "h4", "li"); } }; private TestDocumentContext<Node, Element, Text> cxt; private MutableDocument<Node, Element, Text> doc; @Override protected void setUp() throws Exception { LineContainers.setTopLevelContainerTagname("body"); } public void testTopLevelContainerTagName() { LineContainers.setTopLevelContainerTagname("blah"); assertEquals("blah", LineContainers.topLevelContainerTagname()); LineContainers.setTopLevelContainerTagname("body"); assertEquals("body", LineContainers.topLevelContainerTagname()); try { LineContainers.setTopLevelContainerTagname(null); fail("null should be rejected"); } catch (RuntimeException e) { // ok } } public void testWrappers() { assertEquals("<line></line>foo", LineContainers.debugLineWrap("foo")); assertEquals("<body></body>", LineContainers.debugContainerWrap()); assertEquals("<body><line></line>foo</body>", LineContainers.debugContainerWrap("foo")); assertEquals("<body><line></line>foo<line></line>bar</body>", LineContainers.debugContainerWrap("foo", "bar")); } private class LineTestState { { getDocWithoutSchema("<body><line/>abc<x>def<line id=\"bad\"/></x>." + "<line id=\"2\"/><line id=\"3\"/>ghi</body>jkl"); } Element lc = DocHelper.getElementWithTagName(doc, "body"); Point<Node> beforeLine = Point.before(doc, DocHelper.getElementWithTagName(doc, "line")); Point<Node> afterLine = Point.after(doc, DocHelper.getElementWithTagName(doc, "line")); Point<Node> inAbc = Point.inText(afterLine.getNodeAfter(), 2); Element x = DocHelper.getElementWithTagName(doc, "x"); Point<Node> inX = Point.start(doc, x); Point<Node> afterX = Point.after(doc, x); Point<Node> beforeInvalidLine = Point.before(doc, DocHelper.findElementById(doc, "bad")); Point<Node> beforeLine2 = Point.before(doc, DocHelper.findElementById(doc, "2")); Point<Node> beforeLine3 = Point.before(doc, DocHelper.findElementById(doc, "3")); Point<Node> endLc = Point.<Node>end(lc); Point<Node> inGhi = Point.inText(endLc.getContainer().getLastChild(), 2); Point<Node> endGhi = Point.inText(endLc.getContainer().getLastChild(), 3); } public void testEndOfLineCheck() { LineTestState s = new LineTestState(); // Consider having this be satisfied: // assertFalse(LineContainers.isAtLineEnd(doc, s.beforeLine)); assertTrue(LineContainers.isAtLineEnd(doc, s.beforeLine2)); assertTrue(LineContainers.isAtLineEnd(doc, s.beforeLine3)); assertFalse(LineContainers.isAtLineEnd(doc, s.inAbc)); assertFalse(LineContainers.isAtLineEnd(doc, s.inX)); s.afterX = doc.deleteRange(s.afterX, s.beforeLine2).getSecond(); assertTrue(LineContainers.isAtLineEnd(doc, s.afterX)); assertFalse(LineContainers.isAtLineEnd(doc, s.inGhi)); assertTrue(LineContainers.isAtLineEnd(doc, s.endGhi)); } public void testStartOfLineCheck() { LineTestState s = new LineTestState(); assertTrue(LineContainers.isAtLineStart(doc, s.afterLine)); assertFalse(LineContainers.isAtLineStart(doc, s.beforeLine2)); assertTrue(LineContainers.isAtLineStart(doc, s.beforeLine3)); assertFalse(LineContainers.isAtLineStart(doc, s.inAbc)); assertFalse(LineContainers.isAtLineStart(doc, s.inX)); } public void testEmptyLineCheck() { LineTestState s = new LineTestState(); assertFalse(LineContainers.isAtEmptyLine(doc, s.afterLine)); assertFalse(LineContainers.isAtEmptyLine(doc, s.beforeLine2)); assertTrue(LineContainers.isAtEmptyLine(doc, s.beforeLine3)); assertFalse(LineContainers.isAtEmptyLine(doc, s.inAbc)); assertFalse(LineContainers.isAtEmptyLine(doc, s.inX)); } public void testRounding() { LineTestState s = new LineTestState(); // at node boundaries assertSame(s.beforeLine, LineContainers.roundLocation( doc, Rounding.NONE, s.beforeLine, RoundDirection.RIGHT)); assertSame(s.afterLine, LineContainers.roundLocation( doc, Rounding.NONE, s.afterLine, RoundDirection.RIGHT)); // in text assertSame(s.inAbc, LineContainers.roundLocation( doc, Rounding.NONE, s.inAbc, RoundDirection.RIGHT)); // in a nested element, check it does not jump out assertSame(s.inX, LineContainers.roundLocation( doc, Rounding.NONE, s.inX, RoundDirection.RIGHT)); try { LineContainers.roundLocation(doc, Rounding.WORD, s.afterLine, RoundDirection.RIGHT); fail("You forgot to write a unit test when implementing word rounding!"); } catch (UnsupportedOperationException e) { // ok } try { LineContainers.roundLocation(doc, Rounding.SENTENCE, s.afterLine, RoundDirection.RIGHT); fail("You forgot to write a unit test when implementing sentence rounding!"); } catch (UnsupportedOperationException e) { // ok } // just before the first line assertEquals(s.beforeLine, LineContainers.roundLocation( doc, Rounding.LINE, s.beforeLine, RoundDirection.RIGHT)); // just after the preceding line assertEquals(s.beforeLine2, LineContainers.roundLocation( doc, Rounding.LINE, s.afterLine, RoundDirection.RIGHT)); // in text assertEquals(s.beforeLine2, LineContainers.roundLocation( doc, Rounding.LINE, s.inAbc, RoundDirection.RIGHT)); // *does* jump out assertEquals(s.beforeLine2, LineContainers.roundLocation( doc, Rounding.LINE, s.inX, RoundDirection.RIGHT)); // ignore lines not direct children of the line container assertEquals(s.beforeLine2, LineContainers.roundLocation( doc, Rounding.LINE, s.beforeInvalidLine, RoundDirection.RIGHT)); // just before a line with preceding content assertEquals(s.beforeLine2, LineContainers.roundLocation( doc, Rounding.LINE, s.beforeLine2, RoundDirection.RIGHT)); // just before a line with an immediately preceeding line assertEquals(s.beforeLine3, LineContainers.roundLocation( doc, Rounding.LINE, s.beforeLine3, RoundDirection.RIGHT)); // inside text at the end assertEquals(s.endLc, LineContainers.roundLocation( doc, Rounding.LINE, s.inGhi, RoundDirection.RIGHT)); // just before the end assertEquals(s.endLc, LineContainers.roundLocation( doc, Rounding.LINE, s.endLc, RoundDirection.RIGHT)); // outside of line container is invalid assertNull(LineContainers.roundLocation( doc, Rounding.LINE, Point.before(doc, s.lc), RoundDirection.RIGHT)); assertNull(LineContainers.roundLocation( doc, Rounding.LINE, Point.after(doc, s.lc), RoundDirection.RIGHT)); assertNull(LineContainers.roundLocation( doc, Rounding.LINE, Point.inText(s.lc.getNextSibling(), 2), RoundDirection.RIGHT)); } public void testInsertLine() { LineTestState s; // at node boundaries s = new LineTestState(); checkInsertLine(s.beforeLine, Rounding.NONE, s.beforeLine); s = new LineTestState(); checkInsertLine(s.afterLine, Rounding.NONE, s.afterLine); // in text s = new LineTestState(); checkInsertLine(s.inAbc, Rounding.NONE, s.inAbc); // in a nested element, check it *does* jump out s = new LineTestState(); checkInsertLine(s.afterX, Rounding.NONE, s.inX); // just before the first line s = new LineTestState(); checkInsertLine(s.beforeLine, Rounding.LINE, s.beforeLine); // just after the preceding line s = new LineTestState(); checkInsertLine(s.beforeLine2, Rounding.LINE, s.afterLine); // in text s = new LineTestState(); checkInsertLine(s.beforeLine2, Rounding.LINE, s.inAbc); // *does* jump out s = new LineTestState(); checkInsertLine(s.beforeLine2, Rounding.LINE, s.inX); // ignore lines not direct children of the line container s = new LineTestState(); checkInsertLine(s.beforeLine2, Rounding.LINE, s.beforeInvalidLine); // just before a line with preceding content s = new LineTestState(); checkInsertLine(s.beforeLine2, Rounding.LINE, s.beforeLine2); // just before a line with an immediately preceeding line s = new LineTestState(); checkInsertLine(s.beforeLine3, Rounding.LINE, s.beforeLine3); // inside text at the end s = new LineTestState(); checkInsertLine(s.endLc, Rounding.LINE, s.inGhi); // just before the end s = new LineTestState(); checkInsertLine(s.endLc, Rounding.LINE, s.endLc); // append s = new LineTestState(); checkAppendLine(s.endLc, null); s = new LineTestState(); checkAppendLine(s.endLc, XmlStringBuilder.createText("blah").wrap("y")); getDocWithoutSchema(""); LineContainers.appendLine(doc, XmlStringBuilder.createText("blah").wrap("y")); assertEquals("<body><line></line><y>blah</y></body>", XmlStringBuilder.innerXml(doc).toString()); getDocWithoutSchema("<x>abc</x>"); LineContainers.appendLine(doc, XmlStringBuilder.createText("blah").wrap("y")); assertEquals("<x>abc</x><body><line></line><y>blah</y></body>", XmlStringBuilder.innerXml(doc).toString()); // outside of line container is invalid try { s = new LineTestState(); LineContainers.insertLine(doc, Rounding.LINE, Point.before(doc, s.lc)); fail("Expected invalid location exception"); } catch (IllegalArgumentException iae) { // ok } try { s = new LineTestState(); LineContainers.insertLine(doc, Rounding.LINE, Point.after(doc, s.lc)); fail("Expected invalid location exception"); } catch (IllegalArgumentException iae) { // ok } try { s = new LineTestState(); LineContainers.insertLine(doc, Rounding.LINE, Point.inText(s.lc.getNextSibling(), 2)); fail("Expected invalid location exception"); } catch (IllegalArgumentException iae) { // ok } } public void testAppendLineWithAttributes() { getDocWithSchema(""); LineContainers.appendLine(doc, XmlStringBuilder.createText("hi"), new AttributesImpl("t", "h2")); assertEquals("h2", doc.getAttribute(DocHelper.getElementWithTagName(doc, "line"), "t")); getDocWithSchema("<body><line/>abc</body>"); LineContainers.appendLine(doc, XmlStringBuilder.createText("hi"), new AttributesImpl("id", "2", "t", "h2")); assertEquals("h2", doc.getAttribute(DocHelper.findElementById(doc, "2"), "t")); } public void testAppendObeysSchema() { getDocWithSchema(""); LineContainers.appendLine(doc, XmlStringBuilder.createText("hi")); } public void testInsertInto() { getDocWithSchema("<body><line/>abc</body>"); Point<Node> afterLine = Point.after(doc, DocHelper.getElementWithTagName(doc, "line")); LineContainers.insertInto(doc, afterLine, XmlStringBuilder.createText("blah").wrap("input")); assertEquals("<body><line></line><input>blah</input>abc</body>", XmlStringBuilder.innerXml(doc).toString()); getDocWithSchema("<body><line/>abc</body>"); Point<Node> beforeLine = Point.before(doc, DocHelper.getElementWithTagName(doc, "line")); LineContainers.insertInto(doc, beforeLine, XmlStringBuilder.createText("blah").wrap("input")); assertEquals("<body><line></line><input>blah</input><line></line>abc</body>", XmlStringBuilder.innerXml(doc).toString()); } public void testInsertContentIntoLineStart() { getDocWithSchema("<body><line/>abc</body>"); LineContainers.insertContentIntoLineStart(doc, DocHelper.getElementWithTagName(doc, "line"), XmlStringBuilder.createText("blah").wrap("input")); assertEquals("<body><line></line><input>blah</input>abc</body>", XmlStringBuilder.innerXml(doc).toString()); } public void testInsertContentIntoLineEnd() { getDocWithSchema("<body><line/>abc</body>"); LineContainers.insertContentIntoLineEnd(doc, DocHelper.getElementWithTagName(doc, "line"), XmlStringBuilder.createText("blah").wrap("input")); assertEquals("<body><line></line>abc<input>blah</input></body>", XmlStringBuilder.innerXml(doc).toString()); } public void testAppendToLastLine() { getDocWithSchema("<body><line/>abc</body>"); LineContainers.appendToLastLine(doc, XmlStringBuilder.createText("blah").wrap("input")); assertEquals("<body><line></line>abc<input>blah</input></body>", XmlStringBuilder.innerXml(doc).toString()); getDocWithoutSchema(""); LineContainers.appendToLastLine(doc, XmlStringBuilder.createText("blah").wrap("input")); assertEquals("<body><line></line><input>blah</input></body>", XmlStringBuilder.innerXml(doc).toString()); } public void testGetRelatedLineElement() { LineTestState s = new LineTestState(); Element line1 = DocHelper.getElementWithTagName(doc, "line"); Element line2 = DocHelper.findElementById(doc, "2"); Element line3 = DocHelper.findElementById(doc, "3"); getDocWithoutSchema("<lc><line/>abc<x>def<line id=\"bad\"/></x>." + "<line id=\"2\"/><line id=\"3\"/>ghi</lc>jkl"); assertNull(LineContainers.getRelatedLineElement(doc, Point.start(doc, s.lc))); assertNull(LineContainers.getRelatedLineElement(doc, s.beforeLine)); assertSame(line1, LineContainers.getRelatedLineElement(doc, s.afterLine)); assertSame(line1, LineContainers.getRelatedLineElement(doc, s.inAbc)); assertSame(line1, LineContainers.getRelatedLineElement(doc, s.inX)); assertSame(line1, LineContainers.getRelatedLineElement(doc, s.afterX)); assertSame(line1, LineContainers.getRelatedLineElement(doc, s.beforeInvalidLine)); assertSame(line1, LineContainers.getRelatedLineElement(doc, s.beforeLine2)); assertSame(line2, LineContainers.getRelatedLineElement(doc, s.beforeLine3)); assertSame(line3, LineContainers.getRelatedLineElement(doc, s.inGhi)); assertSame(line3, LineContainers.getRelatedLineElement(doc, Point.end((Node)s.lc))); } public void testGetLineRanges() { getDocWithSchema(""); List<Range> ranges = LineContainers.getLineRanges(doc); assertEquals(0, ranges.size()); getDocWithSchema("<body><line/></body>"); checkLineRanges(""); getDocWithSchema("<body><line/>abc</body>"); checkLineRanges("abc"); getDocWithSchema("<body><line/>abc<input/>def</body>"); checkLineRanges("abcdef"); getDocWithSchema("<body><line/>abc<input>_</input>def</body>"); checkLineRanges("abc_def"); getDocWithSchema("<body><line/>abc<line/>def</body>"); checkLineRanges("abc", "def"); getDocWithSchema("<body><line/>a day<line/> late and <line/>a dollar<line/> short</body>"); checkLineRanges("a day", " late and ", "a dollar", " short"); } public void testDeleteLine() { getDocWithSchema("<body><line/>foo</body>"); LineContainers.deleteLine(doc, DocHelper.getElementWithTagName(doc, "line")); assertEquals("<body><line></line></body>", XmlStringBuilder.innerXml(doc).toString()); getDocWithoutSchema("<body><line><x/></line>foo</body>"); LineContainers.deleteLine(doc, DocHelper.getElementWithTagName(doc, "line")); assertEquals("<body><line></line></body>", XmlStringBuilder.innerXml(doc).toString()); getDocWithSchema("<body><line/>foo<line/>bar</body>"); LineContainers.deleteLine(doc, DocHelper.getElementWithTagName(doc, "line")); assertEquals("<body><line></line>bar</body>", XmlStringBuilder.innerXml(doc).toString()); getDocWithSchema("<body><line/>foo<line id=\"2\"/>bar<line/>baz</body>"); LineContainers.deleteLine(doc, DocHelper.findElementById(doc, "2")); assertEquals("<body><line></line>foo<line></line>baz</body>", XmlStringBuilder.innerXml(doc).toString()); getDocWithoutSchema("<x/><body><line/>foo</body>"); try { LineContainers.deleteLine(doc, DocHelper.getElementWithTagName(doc, "x")); fail("Did not reject non-line element"); } catch (IllegalArgumentException e) { // ok } assertEquals("<x></x><body><line></line>foo</body>", XmlStringBuilder.innerXml(doc).toString()); } private void checkLineRanges(String ... expectedLines) { List<Range> ranges = LineContainers.getLineRanges(doc); assertEquals(expectedLines.length, ranges.size()); int i = 0; for (Range r : ranges) { Point<Node> start = doc.locate(r.getStart()); Point<Node> end = doc.locate(r.getStart()); String expectedLine = expectedLines[i++]; assertEquals(expectedLine, DocHelper.getText(doc, doc, r.getStart(), r.getEnd())); } } // Delete this after 2009/09/15 public void testRejectsParagraphs() { getDocWithoutSchema("<p>blah</p>"); try { LineContainers.appendLine(doc, XmlStringBuilder.createText("yep")); fail("Did not reject paragraph doc"); } catch (IllegalArgumentException e) { // ok } } private void checkInsertLine(Point<Node> expectedLocation, Rounding rounding, Point<Node> location) { checkInsertLineish(expectedLocation, rounding, location, false, 0); } private void checkInsertLineish(Point<Node> expectedPoint, Rounding rounding, Point<Node> location, boolean useParagraphs, int add) { int expectedLocation = doc.getLocation(expectedPoint) + add; Element el = LineContainers.insertLine(doc, rounding, location); assertTrue(LineContainers.isLineElement(doc, el)); assertEquals(expectedLocation, doc.getLocation(el)); } private void checkAppendLine(Point<Node> expectedPoint, XmlStringBuilder content) { checkAppendLineish(expectedPoint, content, false); } private void checkAppendLineish(Point<Node> expectedPoint, XmlStringBuilder content, boolean useParagraphs) { int expectedLocation = doc.getLocation(expectedPoint); Element el = LineContainers.appendLine(doc, content); if (content == null) { content = XmlStringBuilder.createEmpty(); } assertTrue(LineContainers.isLineElement(doc, el)); XmlStringBuilderDoc<Node, Element, Text> siblingContent = XmlStringBuilder.createEmpty(doc); for (Node n = el.getNextSibling(); n != null; n = n.getNextSibling()) { siblingContent.appendNode(n); } assertEquals(content, siblingContent); assertEquals(expectedLocation, doc.getLocation(el)); } private MutableDocument<Node, Element, Text> getDocWithoutSchema(String innerXml) { cxt = ContextProviders.createTestPojoContext(innerXml, null, null, null, DocumentSchema.NO_SCHEMA_CONSTRAINTS); return doc = cxt.document(); } private MutableDocument<Node, Element, Text> getDocWithSchema(String innerXml) { cxt = ContextProviders.createTestPojoContext(innerXml, null, null, null, new DocumentSchema() { @Override public List<String> getRequiredInitialChildren(String typeOrNull) { return SCHEMA.getRequiredInitialChildren(typeOrNull); } @Override public boolean permitsAttribute(String type, String attributeName) { return (type.equals("line") && attributeName.equals("id")) || SCHEMA.permitsAttribute(type, attributeName); } @Override public boolean permitsAttribute(String type, String attributeName, String attributeValue) { return (type.equals("line") && attributeName.equals("id")) || SCHEMA.permitsAttribute(type, attributeName, attributeValue); } @Override public boolean permitsChild(String parentTypeOrNull, String childType) { return SCHEMA.permitsChild(parentTypeOrNull, childType); } @Override public PermittedCharacters permittedCharacters(String typeOrNull) { return SCHEMA.permittedCharacters(typeOrNull); } }); return doc = cxt.document(); } }