/* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * 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 . */ package complex.sfx2; import com.sun.star.accessibility.XAccessible; import com.sun.star.accessibility.XAccessibleAction; import com.sun.star.awt.Point; import com.sun.star.awt.Size; import com.sun.star.awt.XControl; import com.sun.star.awt.XControlModel; import com.sun.star.awt.XToolkitExperimental; import com.sun.star.beans.NamedValue; import com.sun.star.beans.XPropertySet; import com.sun.star.container.NoSuchElementException; import com.sun.star.container.XChild; import com.sun.star.container.XIndexContainer; import com.sun.star.container.XNameContainer; import com.sun.star.container.XNameReplace; import com.sun.star.container.XSet; import com.sun.star.document.EmptyUndoStackException; import com.sun.star.document.UndoContextNotClosedException; import com.sun.star.document.UndoFailedException; import com.sun.star.document.UndoManagerEvent; import com.sun.star.document.XEmbeddedScripts; import com.sun.star.document.XEventsSupplier; import com.sun.star.document.XUndoAction; import com.sun.star.lang.EventObject; import com.sun.star.lang.IndexOutOfBoundsException; import com.sun.star.lang.XEventListener; import java.lang.reflect.InvocationTargetException; import org.openoffice.test.tools.OfficeDocument; import com.sun.star.document.XUndoManagerSupplier; import com.sun.star.document.XUndoManager; import com.sun.star.document.XUndoManagerListener; import com.sun.star.drawing.XControlShape; import com.sun.star.drawing.XDrawPage; import com.sun.star.drawing.XDrawPageSupplier; import com.sun.star.drawing.XShapes; import com.sun.star.lang.XComponent; import com.sun.star.lang.XMultiServiceFactory; import com.sun.star.lang.XServiceInfo; import com.sun.star.lang.XSingleComponentFactory; import com.sun.star.lang.XTypeProvider; import com.sun.star.script.ScriptEventDescriptor; import com.sun.star.script.XEventAttacherManager; import com.sun.star.script.XLibraryContainer; import com.sun.star.task.XJob; import com.sun.star.uno.Exception; import com.sun.star.uno.Type; import com.sun.star.uno.UnoRuntime; import com.sun.star.uno.XComponentContext; import com.sun.star.util.InvalidStateException; import com.sun.star.util.NotLockedException; import com.sun.star.view.XControlAccess; import complex.sfx2.undo.CalcDocumentTest; import complex.sfx2.undo.ChartDocumentTest; import complex.sfx2.undo.DocumentTest; import complex.sfx2.undo.DrawDocumentTest; import complex.sfx2.undo.ImpressDocumentTest; import complex.sfx2.undo.WriterDocumentTest; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Iterator; import java.util.Stack; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; import org.openoffice.test.OfficeConnection; import org.openoffice.test.tools.DocumentType; import org.openoffice.test.tools.SpreadsheetDocument; /** * Unit test for the UndoManager API * */ public class UndoManager { @Before public void beforeTest() throws com.sun.star.uno.Exception { m_currentTestCase = null; m_currentDocument = null; m_undoListener = null; // at our service factory, insert a new factory for our CallbackComponent // this will allow the Basic code in our test documents to call back into this test case // here, by just instantiating this service final XSet globalFactory = UnoRuntime.queryInterface( XSet.class, getORB() ); m_callbackFactory = new CallbackComponentFactory(); globalFactory.insert( m_callbackFactory ); } @Test public void checkWriterUndo() throws Exception { m_currentTestCase = new WriterDocumentTest( getORB() ); impl_checkUndo(); } //FIXME fails fdo#35663 @Test public void checkCalcUndo() throws java.lang.Exception { m_currentTestCase = new CalcDocumentTest( getORB() ); impl_checkUndo(); } @Test public void checkDrawUndo() throws Exception { m_currentTestCase = new DrawDocumentTest( getORB() ); impl_checkUndo(); } @Test public void checkImpressUndo() throws Exception { m_currentTestCase = new ImpressDocumentTest( getORB() ); impl_checkUndo(); } @Test public void checkChartUndo() throws Exception { m_currentTestCase = new ChartDocumentTest( getORB() ); impl_checkUndo(); } @Test public void checkBrokenScripts() throws com.sun.star.uno.Exception, InterruptedException { System.out.println( "testing: broken scripts" ); m_currentDocument = OfficeDocument.blankDocument( getORB(), DocumentType.CALC ); m_undoListener = new UndoListener(); getUndoManager().addUndoManagerListener( m_undoListener ); impl_setupBrokenBasicScript(); final String scriptURI = "vnd.sun.star.script:default.callbacks.brokenScript?language=Basic&location=document"; // scenario 1: Pressing a button which is bound to execute the script // (This is one of the many cases where SfxObjectShell::CallXScript is invoked) // set up the button final XPropertySet buttonModel = impl_setupButton(); buttonModel.setPropertyValue( "Label", "exec broken script" ); impl_assignScript( buttonModel, "XActionListener", "actionPerformed", scriptURI ); // switch the doc's view to form alive mode (so the button will actually work) m_currentDocument.getCurrentView().dispatch( ".uno:SwitchControlDesignMode" ); XToolkitExperimental xToolkit = UnoRuntime.queryInterface( XToolkitExperimental.class, getORB().createInstance("com.sun.star.awt.Toolkit")); xToolkit.processEventsToIdle(); // click the button m_callbackCalled = false; impl_clickButton( buttonModel ); // the macro is executed asynchronously by the button, so wait at most 2 seconds for the callback to be // triggered impl_waitFor( m_callbackCondition, 20000 ); // check the callback has actually been called assertTrue( "clicking the test button did not work as expected - basic script not called", m_callbackCalled ); // again, since the script is executed asynchronously, we might arrive here while its execution // is not completely finished. Give OOo another (at most) 2 seconds to finish it. m_undoListener.waitForAllContextsClosed( 20000 ); // assure that the Undo Context Depth of the doc is still "0": The Basic script entered such a // context, and didn't close it (thus it is broken), but the application framework should have // auto-closed the context after the macro finished. assertEquals( "undo context was not auto-closed as expected", 0, m_undoListener.getCurrentUndoContextDepth() ); // scenario 2: dispatching the script URL. Technically, this is equivalent to configuring the // script into a menu or toolbar, and selecting the respective menu/toolbar item m_callbackCalled = false; m_currentDocument.getCurrentView().dispatch( scriptURI ); assertTrue( "dispatching the Script URL did not work as expected - basic script not called", m_callbackCalled ); // same as above: The script didn't close the context, but the OOo framework should have assertEquals( "undo context was not auto-closed as expected", 0, m_undoListener.getCurrentUndoContextDepth() ); // scenario 3: assigning the script to some document event, and triggering this event final XEventsSupplier eventSupplier = UnoRuntime.queryInterface( XEventsSupplier.class, m_currentDocument.getDocument() ); final XNameReplace events = UnoRuntime.queryInterface( XNameReplace.class, eventSupplier.getEvents() ); final NamedValue[] scriptDescriptor = new NamedValue[] { new NamedValue( "EventType", "Script" ), new NamedValue( "Script", scriptURI ) }; events.replaceByName( "OnViewCreated", scriptDescriptor ); // The below doesn't work: event notification is broken in m96, see https://bz.apache.org/ooo/show_bug.cgi?id=116313 m_callbackCalled = false; m_currentDocument.getCurrentView().dispatch( ".uno:NewWindow" ); assertTrue( "triggering an event did not work as expected - basic script not called", m_callbackCalled ); // same as above: The script didn't close the context, but the OOo framework should have assertEquals( "undo context was not auto-closed as expected", 0, m_undoListener.getCurrentUndoContextDepth() ); // scenario 4: let the script enter an Undo context, but not close it, as usual. // Additionally, let the script close the document - the OOo framework code which cares for // auto-closing of Undo contexts should survive this, ideally ... m_closeAfterCallback = true; m_callbackCalled = false; m_currentDocument.getCurrentView().dispatch( scriptURI ); assertTrue( m_callbackCalled ); assertTrue( "The Basic script should have closed the document.", m_undoListener.isDisposed() ); m_currentDocument = null; } @Test public void checkSerialization() throws com.sun.star.uno.Exception, InterruptedException { System.out.println( "testing: request serialization" ); m_currentDocument = OfficeDocument.blankDocument( getORB(), DocumentType.CALC ); final XUndoManager undoManager = getUndoManager(); final int threadCount = 10; final int actionsPerThread = 10; final int actionCount = threadCount * actionsPerThread; // add some actions to the UndoManager, each knowing its position on the stack final Object lock = new Object(); final Integer actionsUndone[] = new Integer[] { 0 }; for ( int i=actionCount; i>0; ) undoManager.addUndoAction( new CountingUndoAction( --i, lock, actionsUndone ) ); // some concurrent threads which undo the actions Thread[] threads = new Thread[threadCount]; for ( int i=0; i m_activeUndoContexts = new Stack(); private String m_mostRecentlyAddedAction = null; private String m_mostRecentlyUndone = null; private final Object m_allContextsClosedCondition = new Object(); } private void impl_checkUndo() throws Exception { System.out.println( "testing: " + m_currentTestCase.getDocumentDescription() ); m_currentDocument = m_currentTestCase.getDocument(); m_currentTestCase.initializeDocument(); m_currentTestCase.verifyInitialDocumentState(); final XUndoManager undoManager = getUndoManager(); undoManager.clear(); assertFalse( "clearing the Undo manager should result in the impossibility to undo anything", undoManager.isUndoPossible() ); assertFalse( "clearing the Undo manager should result in the impossibility to redo anything", undoManager.isRedoPossible() ); m_undoListener = new UndoListener(); undoManager.addUndoManagerListener( m_undoListener ); impl_testSingleModification( undoManager ); impl_testMultipleModifications( undoManager ); impl_testCustomUndoActions( undoManager ); impl_testLocking( undoManager ); impl_testNestedContexts( undoManager ); impl_testErrorHandling( undoManager ); impl_testContextHandling( undoManager ); impl_testStackHandling( undoManager ); impl_testClearance( undoManager ); impl_testHiddenContexts( undoManager ); // close the document, ensure the Undo manager listener gets notified m_currentTestCase.closeDocument(); m_currentTestCase = null; m_currentDocument = null; assertTrue( "document is closed, but the UndoManagerListener has not been notified of the disposal", m_undoListener.isDisposed() ); } private void impl_testSingleModification( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception { m_currentTestCase.doSingleModification(); m_currentTestCase.verifySingleModificationDocumentState(); // undo the modification, ensure the listener got the proper notifications assertEquals( "We did not yet do a undo!", 0, m_undoListener.getUndoActionCount() ); i_undoManager.undo(); assertEquals( "A simple undo does not result in the proper Undo count.", 1, m_undoListener.getUndoActionCount() ); // verify the document is in its initial state, again m_currentTestCase.verifyInitialDocumentState(); // redo the modification, ensure the listener got the proper notifications assertEquals( "did not yet do a redo!", 0, m_undoListener.getRedoActionCount() ); i_undoManager.redo(); assertEquals( "did a redo, but got no notification of it!", 1, m_undoListener.getRedoActionCount() ); // ensure the document is in the proper state, again m_currentTestCase.verifySingleModificationDocumentState(); // now do an Undo via the UI (aka the dispatch API), and see if this works, and notifies the listener as // expected m_currentTestCase.getDocument().getCurrentView().dispatch( ".uno:Undo" ); m_currentTestCase.verifyInitialDocumentState(); assertEquals( "UI-Undo does not notify the listener", 2, m_undoListener.getUndoActionCount() ); } private void impl_testMultipleModifications( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception { m_undoListener.reset(); assertEquals( "unexpected initial undo context depth", 0, m_undoListener.getCurrentUndoContextDepth() ); i_undoManager.enterUndoContext( "Batch Changes" ); assertEquals( "unexpected undo context depth after entering a context", 1, m_undoListener.getCurrentUndoContextDepth() ); assertEquals( "entering an Undo context has not been notified properly", "Batch Changes", m_undoListener.getCurrentUndoContextTitle() ); final int modifications = m_currentTestCase.doMultipleModifications(); assertEquals( "unexpected number of undo actions while doing batch changes to the document", modifications, m_undoListener.getUndoActionsAdded() ); assertEquals( "seems the document operations touched the undo context depth", 1, m_undoListener.getCurrentUndoContextDepth() ); i_undoManager.leaveUndoContext(); assertEquals( "unexpected undo context depth after leaving the last context", 0, m_undoListener.getCurrentUndoContextDepth() ); assertEquals( "no Undo done, yet - still the listener has been notified of an Undo action", 0, m_undoListener.getUndoActionCount() ); i_undoManager.undo(); assertEquals( "Just did an undo - the listener should have been notified", 1, m_undoListener.getUndoActionCount() ); m_currentTestCase.verifyInitialDocumentState(); } private void impl_testCustomUndoActions( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception { i_undoManager.clear(); m_undoListener.reset(); assertFalse( "undo stack not empty after clearing the undo manager", i_undoManager.isUndoPossible() ); assertFalse( "redo stack not empty after clearing the undo manager", i_undoManager.isRedoPossible() ); assertArrayEquals( ">0 descriptions for an empty undo stack?", new String[0], i_undoManager.getAllUndoActionTitles() ); assertArrayEquals( ">0 descriptions for an empty redo stack?", new String[0], i_undoManager.getAllRedoActionTitles() ); // add two actions, one directly, one within a context final CustomUndoAction action1 = new CustomUndoAction( "UndoAction1" ); i_undoManager.addUndoAction( action1 ); assertEquals( "Adding an undo action not observed by the listener", 1, m_undoListener.getUndoActionsAdded() ); assertEquals( "Adding an undo action did not notify the proper title", action1.getTitle(), m_undoListener.getMostRecentlyAddedActionTitle() ); final String contextTitle = "Undo Context"; i_undoManager.enterUndoContext( contextTitle ); final CustomUndoAction action2 = new CustomUndoAction( "UndoAction2" ); i_undoManager.addUndoAction( action2 ); assertEquals( "Adding an undo action not observed by the listener", 2, m_undoListener.getUndoActionsAdded() ); assertEquals( "Adding an undo action did not notify the proper title", action2.getTitle(), m_undoListener.getMostRecentlyAddedActionTitle() ); i_undoManager.leaveUndoContext(); // see if the manager has proper descriptions assertArrayEquals( "unexpected Redo descriptions after adding two actions", new String[0], i_undoManager.getAllRedoActionTitles() ); assertArrayEquals( "unexpected Undo descriptions after adding two actions", new String[]{contextTitle, action1.getTitle()}, i_undoManager.getAllUndoActionTitles() ); // undo one action i_undoManager.undo(); assertEquals( "improper action title notified during programmatic Undo", contextTitle, m_undoListener.getMostRecentlyUndoneTitle() ); assertTrue( "nested custom undo action has not been undone as expected", action2.undoCalled() ); assertFalse( "nested custom undo action has not been undone as expected", action1.undoCalled() ); assertArrayEquals( "unexpected Redo descriptions after undoing a nested custom action", new String[]{contextTitle}, i_undoManager.getAllRedoActionTitles() ); assertArrayEquals( "unexpected Undo descriptions after undoing a nested custom action", new String[]{action1.getTitle()}, i_undoManager.getAllUndoActionTitles() ); // undo the second action, via UI dispatches m_currentTestCase.getDocument().getCurrentView().dispatch( ".uno:Undo" ); assertEquals( "improper action title notified during UI Undo", action1.getTitle(), m_undoListener.getMostRecentlyUndoneTitle() ); assertTrue( "nested custom undo action has not been undone as expected", action1.undoCalled() ); assertArrayEquals( "unexpected Redo descriptions after undoing the second custom action", new String[]{action1.getTitle(), contextTitle}, i_undoManager.getAllRedoActionTitles() ); assertArrayEquals( "unexpected Undo descriptions after undoing the second custom action", new String[0], i_undoManager.getAllUndoActionTitles() ); // check the actions are disposed when the stacks are cleared i_undoManager.clear(); assertTrue( action1.disposed() && action2.disposed() ); } private void impl_testLocking( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception { i_undoManager.reset(); m_undoListener.reset(); // implicit Undo actions, triggered by changes to the document assertFalse( "unexpected initial locking state", i_undoManager.isLocked() ); i_undoManager.lock(); assertTrue( "just locked the manager, why does it lie?", i_undoManager.isLocked() ); m_currentTestCase.doSingleModification(); assertEquals( "when the Undo manager is locked, no implicit additions should happen", 0, m_undoListener.getUndoActionsAdded() ); assertTrue( "Undo manager gets unlocked as a side effect of performing a simple operation", i_undoManager.isLocked() ); i_undoManager.unlock(); assertEquals( "unlock is not expected to add collected actions - they should be discarded", 0, m_undoListener.getUndoActionsAdded() ); assertFalse( "just unlocked the manager, why does it lie?", i_undoManager.isLocked() ); // explicit Undo actions i_undoManager.lock(); i_undoManager.addUndoAction( new CustomUndoAction() ); i_undoManager.unlock(); assertEquals( "explicit Undo actions are expected to be ignored when the manager is locked", 0, m_undoListener.getUndoActionsAdded() ); // Undo contexts while being locked i_undoManager.lock(); i_undoManager.enterUndoContext( "Dummy Context" ); i_undoManager.enterHiddenUndoContext(); assertEquals( "entering Undo contexts should be ignored when the manager is locked", 0, m_undoListener.getCurrentUndoContextDepth() ); i_undoManager.leaveUndoContext(); i_undoManager.leaveUndoContext(); i_undoManager.unlock(); // |unlock| error handling assertFalse( "internal error: manager should not be locked at this point in time", i_undoManager.isLocked() ); boolean caughtExpected = false; try { i_undoManager.unlock(); } catch ( final NotLockedException e ) { caughtExpected = true; } assertTrue( "unlocking the manager when it is not locked should throw", caughtExpected ); } private void impl_testContextHandling( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception { // part I: non-empty contexts i_undoManager.reset(); m_undoListener.reset(); // put one action on the undo and one on the redo stack, as precondition for the following tests final XUndoAction undoAction1 = new CustomUndoAction( "Undo Action 1" ); i_undoManager.addUndoAction( undoAction1 ); final XUndoAction undoAction2 = new CustomUndoAction( "Undo Action 2" ); i_undoManager.addUndoAction( undoAction2 ); i_undoManager.undo(); assertTrue( "precondition for context handling tests not met (1)", i_undoManager.isUndoPossible() ); assertTrue( "precondition for context handling tests not met (2)", i_undoManager.isRedoPossible() ); assertArrayEquals( new String[] { undoAction1.getTitle() }, i_undoManager.getAllUndoActionTitles() ); assertArrayEquals( new String[] { undoAction2.getTitle() }, i_undoManager.getAllRedoActionTitles() ); final String[] expectedRedoActionComments = new String[] { undoAction2.getTitle() }; assertArrayEquals( expectedRedoActionComments, i_undoManager.getAllRedoActionTitles() ); // enter a context i_undoManager.enterUndoContext( "Undo Context" ); // this should not (yet) touch the redo stack assertArrayEquals( expectedRedoActionComments, i_undoManager.getAllRedoActionTitles() ); assertEquals( "unexpected undo context depth after entering a context", 1, m_undoListener.getCurrentUndoContextDepth() ); // add a single action XUndoAction undoAction3 = new CustomUndoAction( "Undo Action 3" ); i_undoManager.addUndoAction( undoAction3 ); // still, the redo stack should be untouched - added at a lower level does not affect it at all assertArrayEquals( expectedRedoActionComments, i_undoManager.getAllRedoActionTitles() ); // while the context is open, its title should already contribute to the stack, ... assertEquals( "Undo Context", i_undoManager.getCurrentUndoActionTitle() ); // ... getAllUndo/RedoActionTitles should operate on the top level, not on the level defined by the open // context, ... assertArrayEquals( new String[] { "Undo Context", undoAction1.getTitle() }, i_undoManager.getAllUndoActionTitles() ); // ... but Undo and Redo should be impossible as long as the context is open assertFalse( i_undoManager.isUndoPossible() ); assertFalse( i_undoManager.isRedoPossible() ); // leave the context, check the listener has been notified properly, and the notified context depth is correct i_undoManager.leaveUndoContext(); assertTrue( m_undoListener.wasContextLeft() ); assertFalse( m_undoListener.wasHiddenContextLeft() ); assertFalse( m_undoListener.hasContextBeenCancelled() ); assertEquals( "unexpected undo context depth leaving a non-empty context", 0, m_undoListener.getCurrentUndoContextDepth() ); // leaving a non-empty context should have cleared the redo stack assertArrayEquals( new String[0], i_undoManager.getAllRedoActionTitles() ); assertTrue( m_undoListener.wasRedoStackCleared() ); // part II: empty contexts i_undoManager.reset(); m_undoListener.reset(); // enter a context, leave it immediately without adding an action to it i_undoManager.enterUndoContext( "Undo Context" ); i_undoManager.leaveUndoContext(); assertFalse( m_undoListener.wasContextLeft() ); assertFalse( m_undoListener.wasHiddenContextLeft() ); assertTrue( m_undoListener.hasContextBeenCancelled() ); assertFalse( "leaving an empty context should silently remove it, and not contribute to the stack", i_undoManager.isUndoPossible() ); } private void impl_testNestedContexts( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception { i_undoManager.reset(); m_undoListener.reset(); i_undoManager.enterUndoContext( "context 1" ); i_undoManager.enterUndoContext( "context 1.1" ); final CustomUndoAction action1 = new CustomUndoAction( "action 1.1.1" ); i_undoManager.addUndoAction( action1 ); i_undoManager.enterUndoContext( "context 1.1.2" ); final CustomUndoAction action2 = new CustomUndoAction( "action 1.1.2.1" ); i_undoManager.addUndoAction( action2 ); i_undoManager.leaveUndoContext(); final CustomUndoAction action3 = new CustomUndoAction( "action 1.1.3" ); i_undoManager.addUndoAction( action3 ); i_undoManager.leaveUndoContext(); i_undoManager.leaveUndoContext(); final CustomUndoAction action4 = new CustomUndoAction( "action 1.2" ); i_undoManager.addUndoAction( action4 ); i_undoManager.undo(); assertEquals( "undoing a single action notifies a wrong title", action4.getTitle(), m_undoListener.getMostRecentlyUndoneTitle() ); assertTrue( "custom Undo not called", action4.undoCalled() ); assertFalse( "too many custom Undos called", action1.undoCalled() || action2.undoCalled() || action3.undoCalled() ); i_undoManager.undo(); assertTrue( "nested actions not properly undone", action1.undoCalled() && action2.undoCalled() && action3.undoCalled() ); } private void impl_testErrorHandling( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception { i_undoManager.reset(); m_undoListener.reset(); // try retrieving the comments for the current Undo/Redo - this should fail boolean caughtExpected = false; try { i_undoManager.getCurrentUndoActionTitle(); } catch( final EmptyUndoStackException e ) { caughtExpected = true; } assertTrue( "trying the title of the current Undo action is expected to fail for an empty stack", caughtExpected ); caughtExpected = false; try { i_undoManager.getCurrentRedoActionTitle(); } catch( final EmptyUndoStackException e ) { caughtExpected = true; } assertTrue( "trying the title of the current Redo action is expected to fail for an empty stack", caughtExpected ); caughtExpected = false; try { i_undoManager.undo(); } catch ( final EmptyUndoStackException e ) { caughtExpected = true; } assertTrue( "undo should throw if no Undo action is on the stack", caughtExpected ); caughtExpected = false; try { i_undoManager.redo(); } catch ( final EmptyUndoStackException e ) { caughtExpected = true; } assertTrue( "redo should throw if no Redo action is on the stack", caughtExpected ); caughtExpected = false; try { i_undoManager.leaveUndoContext(); } catch ( final InvalidStateException e ) { caughtExpected = true; } assertTrue( "leaveUndoContext should throw if no context is currently open", caughtExpected ); caughtExpected = false; try { i_undoManager.addUndoAction( null ); } catch ( com.sun.star.lang.IllegalArgumentException e ) { caughtExpected = true; } assertTrue( "adding a NULL action should be rejected", caughtExpected ); i_undoManager.reset(); i_undoManager.addUndoAction( new CustomUndoAction() ); i_undoManager.addUndoAction( new CustomUndoAction() ); i_undoManager.undo(); i_undoManager.enterUndoContext( "Undo Context" ); // those methods should fail when a context is open: final String[] methodNames = new String[] { "undo", "redo", "clear", "clearRedo" }; for ( int i=0; i