package com.google.collide.shared.ot; import static com.google.collide.shared.ot.DocOpTestUtils.assertDelete; import static com.google.collide.shared.ot.DocOpTestUtils.assertInsert; import static com.google.collide.shared.ot.DocOpTestUtils.assertRetain; import static com.google.collide.shared.ot.DocOpTestUtils.assertRetainLine; import static com.google.collide.shared.ot.DocOpTestUtils.assertSize; import com.google.collide.dto.DocOp; import com.google.collide.dto.server.ServerDocOpFactory; import com.google.collide.dto.shared.DocOpFactory; import com.google.collide.json.shared.JsonArray; import com.google.collide.shared.document.Document; import com.google.collide.shared.document.Document.TextListener; import com.google.collide.shared.document.Line; import com.google.collide.shared.document.TextChange; import com.google.collide.shared.document.util.LineUtils; import com.google.collide.shared.util.StringUtils; import junit.framework.TestCase; /** * Tests for general document operation methods. * */ public class DocOpTests extends TestCase { private static final String[] LINES = {"Hello world\n", "Foo bar\n", "Something else\n"}; private DocOpFactory factory; private Document doc; private DocOpBuilder builder; private TerseDocOpBuilder b; public void testDocOpCapturerRetainCompacting() { DocOpCapturer c = new DocOpCapturer(factory, true); c.retain(5, true); c.retain(4, false); assertSize(2, c.getDocOp()); c = new DocOpCapturer(factory, true); c.retain(5, false); c.retain(4, false); assertSize(1, c.getDocOp()); } public void testMultilineTextChangeConversions() { TextChange textChange; DocOp op; textChange = TextChange.createInsertion(doc.getFirstLine(), 0, 0, doc.getFirstLine() .getNextLine(), 1, "Hello world\n"); op = DocOpUtils.createFromTextChange(factory, textChange); assertSize(3, op); assertInsert("Hello world\n", op, 0); // TODO: need to doc how we require this line to be retained // instead of "retain line"ed assertRetain(doc.getFirstLine().getNextLine().getText().length(), true, op, 1); assertRetainLine(2, op, 2); textChange = TextChange.createInsertion(doc.getFirstLine().getNextLine(), 1, 1, doc.getFirstLine() .getNextLine().getNextLine(), 2, "oo\nSomething "); op = DocOpUtils.createFromTextChange(factory, textChange); assertSize(6, op); assertRetainLine(1, op, 0); assertRetain(1, false, op, 1); assertInsert("oo\n", op, 2); assertInsert("Something ", op, 3); assertRetain("else\n".length(), true, op, 4); assertRetainLine(1, op, 5); textChange = TextChange.createInsertion(doc.getFirstLine(), 0, 5, doc.getFirstLine().getNextLine() .getNextLine(), 2, " world\nFoo bar\n"); op = DocOpUtils.createFromTextChange(factory, textChange); assertSize(5, op); assertRetain(5, false, op, 0); assertInsert(" world\n", op, 1); assertInsert("Foo bar\n", op, 2); assertRetain("Something else\n".length(), true, op, 3); assertRetainLine(1, op, 4); textChange = TextChange.createDeletion(doc.getFirstLine(), 0, 3, "Imagine this was a line\n"); op = DocOpUtils.createFromTextChange(factory, textChange); assertSize(4, op); assertRetain(3, false, op, 0); assertDelete("Imagine this was a line\n", op, 1); assertRetain("lo world\n".length(), true, op, 2); assertRetainLine(3, op, 3); textChange = TextChange.createDeletion(doc.getFirstLine().getNextLine(), 1, 3, "A line\nand some "); op = DocOpUtils.createFromTextChange(factory, textChange); assertSize(6, op); assertRetainLine(1, op, 0); assertRetain(3, false, op, 1); assertDelete("A line\n", op, 2); assertDelete("and some ", op, 3); assertRetain(" bar\n".length(), true, op, 4); assertRetainLine(2, op, 5); } public void testRetainTrailingNewLineBehavior() { // Add the to-be-inserted "A" and the "r" that is to be retained doc.insertText(doc.getLastLine(), 0, "Ar"); TextChange textChange; DocOp op; textChange = TextChange.createInsertion(doc.getLastLine(), doc.getLastLineNumber(), 0, doc.getLastLine(), doc.getLastLineNumber(), "A"); op = DocOpUtils.createFromTextChange(factory, textChange); assertSize(3, op); assertRetainLine(3, op, 0); assertInsert("A", op, 1); assertRetain(1, false, op, 2); Line line = doc.getLastLine().getPreviousLine(); textChange = TextChange.createInsertion(line, doc.getLastLineNumber() - 1, 0, line, doc.getLastLineNumber() - 1, "S"); op = DocOpUtils.createFromTextChange(factory, textChange); assertSize(4, op); assertRetainLine(2, op, 0); assertInsert("S", op, 1); assertRetain(line.getText().length() - 1, true, op, 2); assertRetainLine(1, op, 3); } public void testSimpleTextChangeConversions() { Document doc = Document.createFromString("\nThis is\na test\n"); TextChange textChange = TextChange.createInsertion(doc.getFirstLine(), 0, 0, doc.getFirstLine(), 0, "\n"); DocOp op = DocOpUtils.createFromTextChange(factory, textChange); assertSize(3, op); assertInsert("\n", op, 0); assertRetain(8, true, op, 1); // There's an empty line at the end assertRetainLine(2, op, 2); } public void testSingleLineTextChangeConversions() { TextChange textChange; DocOp op; textChange = TextChange.createInsertion(doc.getFirstLine(), 0, 1, doc.getFirstLine(), 0, "ello"); op = DocOpUtils.createFromTextChange(factory, textChange); assertSize(4, op); assertRetain(1, false, op, 0); assertInsert("ello", op, 1); assertRetain(7, true, op, 2); assertRetainLine(3, op, 3); textChange = TextChange.createDeletion(doc.getFirstLine(), 0, 2, "WOOT"); op = DocOpUtils.createFromTextChange(factory, textChange); assertSize(4, op); assertRetain(2, false, op, 0); assertDelete("WOOT", op, 1); assertRetain(10, true, op, 2); assertRetainLine(3, op, 3); } /** * Tests the scenario where the document is: * * <pre> * \n * \n * aa * </pre> * * and we insert a newline at line 0, position 0 */ public void testBuggyScenario1() { doc = Document.createFromString("\n\na"); final DocOp[] opFromListener = new DocOp[1]; doc.getTextListenerRegistrar().add(new TextListener() { @Override public void onTextChange(Document document, JsonArray<TextChange> textChanges) { opFromListener[0] = DocOpUtils.createFromTextChange(factory, textChanges.get(0)); } }); TextChange textChange = doc.insertText(doc.getFirstLine(), 0, "\n"); assertEquals(TextChange.createInsertion(doc.getFirstLine(), 0, 0, doc.getFirstLine() .getNextLine(), 1, "\n"), textChange); DocOp opPostListener = DocOpUtils.createFromTextChange(factory, textChange); DocOpTestUtils.assertDocOpEquals(opPostListener, opFromListener[0]); } /** * Tests the scenario where the document is: * * <pre> * a\n * b\n * c\n * d\n * e * </pre> * * and we delete a\nb\nc\nd. */ public void testBuggyDueToNoRetainWithTrailingNewLine() { doc = Document.createFromString("a\nb\nc\nd\ne"); TextChange textChange = doc.deleteText(doc.getFirstLine(), 0, 0, 7); DocOp op = DocOpUtils.createFromTextChange(factory, textChange); assertSize(6, op); assertDelete("a\n", op, 0); assertDelete("b\n", op, 1); assertDelete("c\n", op, 2); assertDelete("d", op, 3); assertRetain(1, true, op, 4); assertRetainLine(1, op, 5); } /** * <pre> * ?????????? * dAsSafasD * ASasd * DaASdf * DASas * DASs * DdAS * fD * ASD * ASD * ASD * ASD * ASD * AS * DAS * DAS * D * ASD * ASD * ASD * AS? * ? (this maps to a RL(1), so not sure if it was empty or had text) * (this is an empty line) * </pre> */ public void testWhetherDeleteMultilineSelectionInThisCaseCreatesRetainLineToMatchEmptyLastLine() { doc = Document.createFromString( "??????????\ndAsSafasD\nASasd\nDaASdf\nDASas\nDASs\nDdAS\nfD\nASD\nASD\nASD\nASD\n" + "ASD\nAS\nDAS\nDAS\nD\nASD\nASD\nASD\nAS?\n?\n"); assertEquals(23, doc.getLineCount()); int textCount = LineUtils.getTextCount( doc.getFirstLine(), 10, doc.getLastLine().getPreviousLine().getPreviousLine(), 1); TextChange textChange = doc.deleteText(doc.getFirstLine(), 10, textCount); DocOp docOp = DocOpUtils.createFromTextChange(factory, textChange); docOp.toString(); } public void testMissingRetainLineAfterDelete() { { doc = Document.createFromString("\n"); TextChange textChange = doc.deleteText(doc.getFirstLine(), 0, 1); DocOp docOp = DocOpUtils.createFromTextChange(factory, textChange); DocOp expected = b.d("\n").rl(1).b(); DocOpTestUtils.assertDocOpEquals(expected, docOp); } { doc = Document.createFromString("alex\n"); TextChange textChange = doc.deleteText(doc.getFirstLine(), 4, 1); DocOp docOp = DocOpUtils.createFromTextChange(factory, textChange); DocOp expected = b.r(4).d("\n").rl(1).b(); DocOpTestUtils.assertDocOpEquals(expected, docOp); } { doc = Document.createFromString("alex"); TextChange textChange = doc.insertText(doc.getFirstLine(), 4, "\n"); DocOp docOp = DocOpUtils.createFromTextChange(factory, textChange); DocOp expected = b.r(4).i("\n").rl(1).b(); DocOpTestUtils.assertDocOpEquals(expected, docOp); } { doc = Document.createFromString("alex\ntest"); TextChange textChange = doc.deleteText(doc.getFirstLine(), 0, 9); DocOp docOp = DocOpUtils.createFromTextChange(factory, textChange); DocOp expected = b.d("alex\n").d("test").rl(1).b(); DocOpTestUtils.assertDocOpEquals(expected, docOp); } } public void testSimpleConversionWorks() { doc = Document.createFromString("aa\nhh\nii"); TextChange textChange = doc.insertText(doc.getFirstLine().getNextLine(), 0, "hh\nii\n"); DocOp docOpA = DocOpUtils.createFromTextChange(factory, textChange); DocOp expected = b.rl(1).i("hh\n").i("ii\n").eolR(3).rl(1).b(); DocOpTestUtils.assertDocOpEquals(expected, docOpA); textChange = doc.deleteText(doc.getFirstLine().getNextLine().getNextLine(), 2, 6); DocOp docOpB = DocOpUtils.createFromTextChange(factory, textChange); expected = b.rl(2).r(2).d("\n").d("hh\n").d("ii").b(); DocOpTestUtils.assertDocOpEquals(expected, docOpB); } @Override protected void setUp() throws Exception { factory = ServerDocOpFactory.INSTANCE; builder = new DocOpBuilder(factory, false); b = new TerseDocOpBuilder(factory, false); doc = Document.createEmpty(); doc.insertText(doc.getFirstLine(), 0, StringUtils.join(LINES, "")); } }