diff --git a/openide.text/test/unit/src/org/openide/text/UndoRedoWrappingCooperationTest.java b/openide.text/test/unit/src/org/openide/text/UndoRedoWrappingCooperationTest.java new file mode 100644 --- /dev/null +++ b/openide.text/test/unit/src/org/openide/text/UndoRedoWrappingCooperationTest.java @@ -0,0 +1,563 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Microsystems, Inc. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ + + +package org.openide.text; + + + +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.event.UndoableEditEvent; +import javax.swing.event.UndoableEditListener; +import javax.swing.text.*; +import javax.swing.undo.CompoundEdit; +import javax.swing.undo.UndoableEdit; +import org.netbeans.junit.*; +import org.openide.awt.UndoRedo; +import org.openide.util.Exceptions; + +/** + * Testing CES's UndoGroupManager; BEGIN_COMMIT_GROUP, END_COMMIT_GROUP + * + * Also included are tests testSaveDocumentErrorCase and testRedoAfterSave. + * They fail for some base CES functionality. They could be moved + * to UndoRedoCooperationTest. + * + * @author Ernie Rael + */ +public class UndoRedoWrappingCooperationTest extends NbTestCase implements CloneableEditorSupport.Env { + static { + System.setProperty("org.openide.windows.DummyWindowManager.VISIBLE", "false"); + } + /** the support to work with */ + private CES support; + // Env variables + private String content = "Hello"; + private boolean valid = true; + private boolean modified = false; + /** if not null contains message why this document cannot be modified */ + private String cannotBeModified; + private java.util.Date date = new java.util.Date (); + private java.util.List/**/ propL = new java.util.ArrayList (); + private java.beans.VetoableChangeListener vetoL; + + /** Creates new TextTest */ + public UndoRedoWrappingCooperationTest (String s) { + super(s); + } + + protected javax.swing.text.EditorKit createEditorKit() { + return new NbLikeEditorKit(); + } + + // Use these methods with the UndoRedoGroup patch + CompoundEdit beginChunk(Document d) { + // ur().beginUndoGroup(); + sendUndoableEdit(d, CloneableEditorSupport.BEGIN_COMMIT_GROUP); + return null; + } + + void endChunk(Document d, CompoundEdit ce) { + // ur().endUndoGroup(); + sendUndoableEdit(d, CloneableEditorSupport.END_COMMIT_GROUP); + } + + void sendUndoableEdit(Document d, UndoableEdit ue) { + if(d instanceof AbstractDocument) { + UndoableEditListener[] uels = ((AbstractDocument)d).getUndoableEditListeners(); + UndoableEditEvent ev = new UndoableEditEvent(d, ue); + for(UndoableEditListener uel : uels) { + uel.undoableEditHappened(ev); + } + } + } + + // Use these methods with compound edit implementation + // CompoundEdit beginChunk(Document d) { + // CompoundEdit ce = new CompoundEdit(); + // support.getUndoRedo().undoableEditHappened + // (new UndoableEditEvent(d, ce)); + // return ce; + // } + + // void endChunk(Document d, CompoundEdit ce) { + // ce.end(); + // } + + UndoRedo.Manager ur() { + return support.getUndoRedo(); + } + + protected void setUp () { + support = new CES (this, org.openide.util.Lookup.EMPTY); + } + + public void testTrivialChunk() throws Exception { + content = ""; + StyledDocument d = support.openDocument(); + + // same operations as testSingleChunk, + // but don't test modified/canUndo/canRedo state + + CompoundEdit ce = beginChunk(d); + d.insertString(d.getLength(), "a", null); + d.insertString(d.getLength(), "b", null); + endChunk(d, ce); + + assertEquals("data", "ab", d.getText(0, d.getLength())); + + ur().undo(); + assertEquals("after undo data", "", d.getText(0, d.getLength())); + + ur().redo(); + assertEquals("after redo data", "ab", d.getText(0, d.getLength())); + } + + public void testSingleChunk() throws Exception { + content = ""; + StyledDocument d = support.openDocument(); + assertFalse("initially: not modified", support.isModified()); + assertFalse("initially: no undo", ur().canUndo()); + assertFalse("initially: no redo", ur().canRedo()); + + CompoundEdit ce = beginChunk(d); + assertFalse("start chunk: not modified", support.isModified()); + assertFalse("start chunk: no undo", ur().canUndo()); + assertFalse("start chunk: no redo", ur().canRedo()); + + d.insertString(d.getLength(), "a", null); + assertTrue("insert: modified", support.isModified()); + assertTrue("insert: can undo", ur().canUndo()); + assertFalse("insert: no redo", ur().canRedo()); + + d.insertString(d.getLength(), "b", null); + endChunk(d, ce); + assertEquals("chunk: data", "ab", d.getText(0, d.getLength())); + assertTrue("endChunk: modified", support.isModified()); + assertTrue("endChunk: can undo", ur().canUndo()); + assertFalse("endChunk: no redo", ur().canRedo()); + + ur().undo(); + assertEquals("after undo: data", "", d.getText(0, d.getLength())); + assertFalse("undo: not modified", support.isModified()); + assertFalse("undo: no undo", ur().canUndo()); + assertTrue("undo: can redo", ur().canRedo()); + + ur().redo(); + assertEquals("after redo: data", "ab", d.getText(0, d.getLength())); + assertTrue("redo: modified", support.isModified()); + assertTrue("redo: can undo", ur().canUndo()); + assertFalse("redo: no redo", ur().canRedo()); + } + + /** this also tests mixing regular and chunks */ + public void testExtraEndChunk() throws Exception { + content = ""; + StyledDocument d = support.openDocument(); + + CompoundEdit ce = beginChunk(d); + + d.insertString(d.getLength(), "a", null); + d.insertString(d.getLength(), "b", null); + + endChunk(d, ce); + assertEquals("chunk: data", "ab", d.getText(0, d.getLength())); + + endChunk(d, ce); + endChunk(d, ce); + + assertEquals("extraEnd: data", "ab", d.getText(0, d.getLength())); + assertTrue("extraEnd: modified", support.isModified()); + assertTrue("extraEnd: can undo", ur().canUndo()); + assertFalse("extraEnd: no redo", ur().canRedo()); + + d.insertString(d.getLength(), "c", null); + d.insertString(d.getLength(), "d", null); + endChunk(d, ce); + assertEquals("extraEnd2: data", "abcd", d.getText(0, d.getLength())); + ur().undo(); + endChunk(d, ce); + assertEquals("undo1: data", "abc", d.getText(0, d.getLength())); + ur().undo(); + assertEquals("undo2: data", "ab", d.getText(0, d.getLength())); + ur().undo(); + endChunk(d, ce); + assertEquals("undo3: data", "", d.getText(0, d.getLength())); + ur().redo(); + assertEquals("redo1: data", "ab", d.getText(0, d.getLength())); + ur().redo(); + endChunk(d, ce); + assertEquals("redo2: data", "abc", d.getText(0, d.getLength())); + ur().redo(); + assertEquals("redo3: data", "abcd", d.getText(0, d.getLength())); + } + + public void testUndoRedoWhileActiveChunk() throws Exception { + content = ""; + StyledDocument d = support.openDocument(); + CompoundEdit ce = beginChunk(d); + d.insertString(d.getLength(), "a", null); + d.insertString(d.getLength(), "b", null); + + assertEquals("before undo: data", "ab", d.getText(0, d.getLength())); + + ur().undo(); + + // These asserts assume that an undo in the middle of a chunk + // is an undo on the whole chunk so far. + + assertEquals("after undo: data", "", d.getText(0, d.getLength())); + assertFalse("after undo: not modified", support.isModified()); + assertFalse("after undo: no undo", ur().canUndo()); + assertTrue("after undo: can redo", ur().canRedo()); + + // note still in the chunk. + + ur().redo(); + assertEquals("after redo: data", "ab", d.getText(0, d.getLength())); + assertTrue("after redo: modified", support.isModified()); + assertTrue("after redo: can undo", ur().canUndo()); + assertFalse("after redo: no redo", ur().canRedo()); + + ur().undo(); + assertEquals("after undo: data", "", d.getText(0, d.getLength())); + + // note still in the chunk. + + d.insertString(d.getLength(), "c", null); + d.insertString(d.getLength(), "d", null); + endChunk(d, ce); + + assertEquals("after endChunk: data", "cd", d.getText(0, d.getLength())); + assertTrue("after endChunk: modified", support.isModified()); + assertTrue("after endChunk: can undo", ur().canUndo()); + assertFalse("after endChunk: no redo", ur().canRedo()); + + + ur().undo(); + assertEquals("undo after endChunk: data", "", d.getText(0, d.getLength())); + assertFalse("undo after endChunk: not modified", support.isModified()); + assertFalse("undo after endChunk: no undo", ur().canUndo()); + assertTrue("undo after endChunk: can redo", ur().canRedo()); + } + + public void testSaveDocumentWhileActiveChunkCommon(boolean doFailCase) throws Exception { + content = ""; + StyledDocument d = support.openDocument(); + CompoundEdit ce = beginChunk(d); + d.insertString(d.getLength(), "a", null); + d.insertString(d.getLength(), "b", null); + + support.saveDocument (); // creates a separate undoable chunk + assertFalse("save: not modified", support.isModified()); + assertTrue("save: can undo", ur().canUndo()); + assertFalse("save: no redo", ur().canRedo()); + + d.insertString(d.getLength(), "c", null); + d.insertString(d.getLength(), "d", null); + endChunk(d, ce); + + assertEquals("insert, after save: data", "abcd", d.getText(0, d.getLength())); + assertTrue("insert, after save: modified", support.isModified()); + assertTrue("insert, after save: can undo", ur().canUndo()); + assertFalse("insert, after save: no redo", ur().canRedo()); + + ur().undo(); + assertEquals("undo, at save: data", "ab", d.getText(0, d.getLength())); + assertFalse("undo, at save: not modified", support.isModified()); + assertTrue("undo, at save: can undo", ur().canUndo()); + assertTrue("undo, at save: can redo", ur().canRedo()); + + ur().undo(); + assertEquals("undo, before save: data", "", d.getText(0, d.getLength())); + + if(doFailCase) { + // **************************************************************** + // CES BUG??? + assertTrue("undo, before save: modified", support.isModified()); + // **************************************************************** + } + + assertFalse("undo, before save: can undo", ur().canUndo()); + assertTrue("undo, before save: can redo", ur().canRedo()); + + ur().redo(); + assertEquals("redo, at save: data", "ab", d.getText(0, d.getLength())); + assertFalse("redo, at save: not modified", support.isModified()); + assertTrue("redo, at save: can undo", ur().canUndo()); + assertTrue("redo, at save: can redo", ur().canRedo()); + } + + public void testSaveDocumentWhileActiveChunk() throws Exception { + testSaveDocumentWhileActiveChunkCommon(false); + } + + // This fails, below is "testSaveDocumentErrorCase" without chunking, + // it also fails. + // public void testSaveDocumentWhileActiveChunkErroCase() throws Exception { + // testSaveDocumentWhileActiveChunkCommon(true); + // } + + public void testNestedChunks() throws Exception { + content = ""; + StyledDocument d = support.openDocument(); + CompoundEdit ce1 = beginChunk(d); + d.insertString(d.getLength(), "a", null); + d.insertString(d.getLength(), "b", null); + + CompoundEdit ce2 = beginChunk(d); // creates a separate undoable chunk + + d.insertString(d.getLength(), "c", null); + d.insertString(d.getLength(), "d", null); + + endChunk(d, ce1); + + d.insertString(d.getLength(), "e", null); + d.insertString(d.getLength(), "f", null); + + endChunk(d, ce2); + + assertEquals("data", "abcdef", d.getText(0, d.getLength())); + + // following fails if nesting not supported + ur().undo(); + assertEquals("undo1", "abcd", d.getText(0, d.getLength())); + + ur().undo(); + assertEquals("undo2", "ab", d.getText(0, d.getLength())); + + ur().undo(); + assertEquals("undo3", "", d.getText(0, d.getLength())); + } + + // This is testSaveDocumentWhileActiveChunk WITHOUT chunking + public void testSaveDocumentCommon(boolean doFailCase) throws Exception { + content = ""; + StyledDocument d = support.openDocument(); + d.insertString(d.getLength(), "a", null); + + support.saveDocument (); // creates a separate undoable chunk + assertFalse("save: not modified", support.isModified()); + assertTrue("save: can undo", ur().canUndo()); + assertFalse("save: no redo", ur().canRedo()); + + d.insertString(d.getLength(), "b", null); + + assertEquals("insert, after save: data", "ab", d.getText(0, d.getLength())); + assertTrue("insert, after save: modified", support.isModified()); + assertTrue("insert, after save: can undo", ur().canUndo()); + assertFalse("insert, after save: no redo", ur().canRedo()); + + ur().undo(); + assertEquals("undo, at save: data", "a", d.getText(0, d.getLength())); + assertFalse("undo, at save: not modified", support.isModified()); + assertTrue("undo, at save: can undo", ur().canUndo()); + assertTrue("undo, at save: can redo", ur().canRedo()); + + ur().undo(); + assertEquals("undo, before save: data", "", d.getText(0, d.getLength())); + + if(doFailCase) { + // **************************************************************** + // CES BUG??? + assertTrue("undo, before save: modified", support.isModified()); + // **************************************************************** + } + + assertFalse("undo, before save: can undo", ur().canUndo()); + assertTrue("undo, before save: can redo", ur().canRedo()); + + ur().redo(); + assertEquals("redo, at save: data", "a", d.getText(0, d.getLength())); + assertFalse("redo, at save: not modified", support.isModified()); + assertTrue("redo, at save: can undo", ur().canUndo()); + assertTrue("redo, at save: can redo", ur().canRedo()); + } + + public void testSaveDocument() throws Exception { + testSaveDocumentCommon(false); + } + + // NOTE: following fail is issue in CES + public void testSaveDocumentErrorCase() throws Exception { + testSaveDocumentCommon(true); + } + + // NOTE: following fail is issue in CES + public void testRedoAfterSave() throws Exception { + content = ""; + StyledDocument d = support.openDocument(); + d.insertString(d.getLength(), "a", null); + + d.insertString(d.getLength(), "b", null); + + assertEquals("insert: data", "ab", d.getText(0, d.getLength())); + assertTrue("insert: modified", support.isModified()); + assertTrue("insert: can undo", ur().canUndo()); + assertFalse("insert: no redo", ur().canRedo()); + + ur().undo(); + assertEquals("undo: data", "a", d.getText(0, d.getLength())); + assertTrue("undo: modified", support.isModified()); + assertTrue("undo: can undo", ur().canUndo()); + assertTrue("undo: can redo", ur().canRedo()); + + support.saveDocument (); + assertFalse("save: not modified", support.isModified()); + assertTrue("save: can undo", ur().canUndo()); + assertTrue("save: can redo", ur().canRedo()); + + ur().redo(); + assertEquals("redo: data", "ab", d.getText(0, d.getLength())); + assertTrue("redo: modified", support.isModified()); + assertTrue("redo: can undo", ur().canUndo()); + assertFalse("redo: no redo", ur().canRedo()); + } + + // + // Implementation of the CloneableEditorSupport.Env + // + + public synchronized void addPropertyChangeListener(java.beans.PropertyChangeListener l) { + propL.add (l); + } + public synchronized void removePropertyChangeListener(java.beans.PropertyChangeListener l) { + propL.remove (l); + } + + public synchronized void addVetoableChangeListener(java.beans.VetoableChangeListener l) { + assertNull ("This is the first veto listener", vetoL); + vetoL = l; + } + public void removeVetoableChangeListener(java.beans.VetoableChangeListener l) { + assertEquals ("Removing the right veto one", vetoL, l); + vetoL = null; + } + + public org.openide.windows.CloneableOpenSupport findCloneableOpenSupport() { + return support; + } + + public String getMimeType() { + return "text/plain"; + } + + public java.util.Date getTime() { + return date; + } + + public java.io.InputStream inputStream() throws java.io.IOException { + return new java.io.ByteArrayInputStream (content.getBytes ()); + } + public java.io.OutputStream outputStream() throws java.io.IOException { + class ContentStream extends java.io.ByteArrayOutputStream { + public void close () throws java.io.IOException { + super.close (); + content = new String (toByteArray ()); + } + } + + return new ContentStream (); + } + + public boolean isValid() { + return valid; + } + + public boolean isModified() { + return modified; + } + + public void markModified() throws java.io.IOException { + if (cannotBeModified != null) { + IOException e = new IOException (); + Exceptions.attachLocalizedMessage(e, cannotBeModified); + throw e; + } + + modified = true; + } + + public void unmarkModified() { + modified = false; + } + + /** Implementation of the CES */ + private final class CES extends CloneableEditorSupport { + public boolean plain; + + + public CES (Env env, org.openide.util.Lookup l) { + super (env, l); + } + + protected String messageName() { + return "Name"; + } + + protected String messageOpened() { + return "Opened"; + } + + protected String messageOpening() { + return "Opening"; + } + + protected String messageSave() { + return "Save"; + } + + protected String messageToolTip() { + return "ToolTip"; + } + + protected javax.swing.text.EditorKit createEditorKit() { + if (plain) { + return super.createEditorKit (); + } else { + return UndoRedoWrappingCooperationTest.this.createEditorKit (); + } + } + } // end of CES + +}