/* * 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 .
*/
// 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 );
}
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 );
// note: this may be prevented from working by setting // Office::Common::Security::Scripting::AllowedDocumentEventURLs // (checked in SfxEvents_Impl::isScriptURLAllowed())
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;
}
// 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 = newThread[threadCount]; for ( int i=0; i<threadCount; ++i )
{
threads[i] = newThread()
{
@Override publicvoid run()
{ for ( int j=0; j<actionsPerThread; ++j )
{ try { undoManager.undo(); } catch ( final Exception e )
{
fail( "Those dummy actions are not expected to fail." ); return;
}
}
}
};
}
// start the threads for ( int i=0; i<threadCount; ++i )
threads[i].start();
// wait for them to be finished for ( int i=0; i<threadCount; ++i )
threads[i].join();
// ensure all actions have been undone
assertEquals( "not all actions have been undone", actionCount, actionsUndone[0].intValue() );
}
privatevoid impl_setupBrokenBasicScript()
{ try
{ final XEmbeddedScripts embeddedScripts = UnoRuntime.queryInterface( XEmbeddedScripts.class, m_currentDocument.getDocument() ); final XLibraryContainer basicLibs = embeddedScripts.getBasicLibraries(); final XNameContainer basicLib = basicLibs.createLibrary( "default" );
final String brokenScriptCode = "Option Explicit\n" + "\n" + "Sub brokenScript\n" + " Dim callback as Object\n" + " ThisComponent.UndoManager.enterUndoContext( \"" + getCallbackUndoContextTitle() + "\" )\n" + "\n" + " callback = createUnoService( \"" + getCallbackComponentServiceName() + "\" )\n" + " Dim emptyArgs() as new com.sun.star.beans.NamedValue\n" + " Dim result as String\n" + " result = callback.execute( emptyArgs() )\n" + " If result = \"close\" Then\n" + " ThisComponent.close( TRUE )\n" + " End If\n" + "End Sub\n" + "\n";
basicLib.insertByName( "callbacks", brokenScriptCode );
} catch( com.sun.star.uno.Exception e )
{
fail( "caught an exception while setting up the script: " + e.toString() );
}
}
private XPropertySet impl_setupButton() throws com.sun.star.uno.Exception
{ // let the document create a shape final XMultiServiceFactory docAsFactory = UnoRuntime.queryInterface( XMultiServiceFactory.class,
m_currentDocument.getDocument() ); final XControlShape xShape = UnoRuntime.queryInterface( XControlShape.class,
docAsFactory.createInstance( "com.sun.star.drawing.ControlShape" ) );
// position and size of the shape
xShape.setSize( new Size( 28 * 100, 10 * 100 ) );
xShape.setPosition( new Point( 10 * 100, 10 * 100 ) );
// create the form component (the model of a form control) final String sQualifiedComponentName = "com.sun.star.form.component.CommandButton"; final XControlModel controlModel = UnoRuntime.queryInterface( XControlModel.class,
getORB().createInstance( sQualifiedComponentName ) );
// knitt both
xShape.setControl( controlModel );
// add the shape to the shapes collection of the document
SpreadsheetDocument spreadsheetDoc = (SpreadsheetDocument)m_currentDocument; final XDrawPageSupplier suppDrawPage = UnoRuntime.queryInterface( XDrawPageSupplier.class,
spreadsheetDoc.getSheet( 0 ) ); final XDrawPage insertIntoPage = suppDrawPage.getDrawPage();
privatevoid impl_assignScript( final XPropertySet i_controlModel, final String i_interfaceName, final String i_interfaceMethod, final String i_scriptURI ) throws Exception
{ final XChild modelAsChild = UnoRuntime.queryInterface( XChild.class, i_controlModel ); final XIndexContainer parentForm = UnoRuntime.queryInterface( XIndexContainer.class, modelAsChild.getParent() );
final XEventAttacherManager manager = UnoRuntime.queryInterface( XEventAttacherManager.class, parentForm );
int containerPosition = -1; for ( int i = 0; i < parentForm.getCount(); ++i )
{ final XPropertySet child = UnoRuntime.queryInterface( XPropertySet.class, parentForm.getByIndex( i ) ); if ( UnoRuntime.areSame( child, i_controlModel ) )
{
containerPosition = i; break;
}
}
assertFalse( "could not find the given control model within its parent", containerPosition == -1 );
manager.registerScriptEvent( containerPosition, new ScriptEventDescriptor(
i_interfaceName,
i_interfaceMethod, "", "Script",
i_scriptURI
) );
}
privatevoid impl_clickButton( final XPropertySet i_buttonModel ) throws NoSuchElementException, IndexOutOfBoundsException
{ final XControlAccess controlAccess = UnoRuntime.queryInterface( XControlAccess.class,
m_currentDocument.getCurrentView().getController() ); final XControl control = controlAccess.getControl( UnoRuntime.queryInterface( XControlModel.class, i_buttonModel ) ); final XAccessible accessible = UnoRuntime.queryInterface( XAccessible.class, control ); final XAccessibleAction controlActions = UnoRuntime.queryInterface( XAccessibleAction.class, accessible.getAccessibleContext() ); for ( int i=0; i<controlActions.getAccessibleActionCount(); ++i )
{ if (controlActions.getAccessibleActionDescription(i).equals("press"))
{
controlActions.doAccessibleAction(i); return;
}
}
fail("did not find the accessible action named 'press'");
}
privatestaticclass UndoListener implements XUndoManagerListener
{ publicvoid undoActionAdded( UndoManagerEvent i_event )
{
assertFalse( "|undoActionAdded| called after document was disposed", m_isDisposed );
publicvoid enteredContext( UndoManagerEvent i_event )
{
assertFalse( "|enteredContext| called after document was disposed", m_isDisposed );
m_activeUndoContexts.push( i_event.UndoActionTitle );
assertEquals( "different opinions on the context nesting level (after entering)",
m_activeUndoContexts.size(), i_event.UndoContextDepth );
}
publicvoid enteredHiddenContext( UndoManagerEvent i_event )
{
assertFalse( "|enteredHiddenContext| called after document was disposed", m_isDisposed );
m_activeUndoContexts.push( i_event.UndoActionTitle );
assertEquals( "different opinions on the context nesting level (after entering hidden)",
m_activeUndoContexts.size(), i_event.UndoContextDepth );
}
publicvoid leftContext( UndoManagerEvent i_event )
{
assertFalse( "|leftContext| called after document was disposed", m_isDisposed );
assertEquals( "nested undo context descriptions do not match", m_activeUndoContexts.pop(), i_event.UndoActionTitle );
assertEquals( "different opinions on the context nesting level (after leaving)",
m_activeUndoContexts.size(), i_event.UndoContextDepth );
m_leftContext = true;
impl_notifyContextDepth();
}
publicvoid leftHiddenContext( UndoManagerEvent i_event )
{
assertFalse( "|leftHiddenContext| called after document was disposed", m_isDisposed );
assertEquals( "|leftHiddenContext| is not expected to notify an action title", 0, i_event.UndoActionTitle.length() );
m_activeUndoContexts.pop();
assertEquals( "different opinions on the context nesting level (after leaving)",
m_activeUndoContexts.size(), i_event.UndoContextDepth );
m_leftHiddenContext = true;
impl_notifyContextDepth();
}
publicvoid cancelledContext( UndoManagerEvent i_event )
{
assertFalse( "|cancelledContext| called after document was disposed", m_isDisposed );
assertEquals( "|cancelledContext| is not expected to notify an action title", 0, i_event.UndoActionTitle.length() );
m_activeUndoContexts.pop();
assertEquals( "different opinions on the context nesting level (after cancelling)",
m_activeUndoContexts.size(), i_event.UndoContextDepth );
m_cancelledContext = true;
impl_notifyContextDepth();
}
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 );
// 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() );
}
// undo the modification, ensure the listener got the proper notifications
assertEquals( "We did not yet do an 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( "not yet made a redo!", 0, m_undoListener.getRedoActionCount() );
i_undoManager.redo();
assertEquals( "made 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() );
}
privatevoid 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() );
finalint 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();
}
privatevoid 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() );
}
// 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 );
}
privatevoid 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() );
}
privatevoid 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() );
}
// 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<methodNames.length; ++i )
{
caughtExpected = false; try
{
Method method = i_undoManager.getClass().getMethod( methodNames[i], newClass[0] );
method.invoke( i_undoManager, new Object[0] );
} catch ( IllegalAccessException ex ) { } catch ( IllegalArgumentException ex ) { } catch ( InvocationTargetException ex )
{
Throwable targetException = ex.getTargetException();
caughtExpected = ( targetException instanceof UndoContextNotClosedException );
} catch ( NoSuchMethodException ex ) { } catch ( SecurityException ex ) { }
assertTrue( methodNames[i] + " should be rejected when there is an open context", caughtExpected );
}
i_undoManager.leaveUndoContext();
// try Undo actions which fail in their Undo/Redo for ( int i=0; i<4; ++i )
{ finalboolean undo = ( i < 2 ); finalboolean doByAPI = ( i % 2 ) == 0;
i_undoManager.reset();
i_undoManager.addUndoAction( new CustomUndoAction() );
i_undoManager.addUndoAction( new FailingUndoAction( undo ? FAIL_UNDO : FAIL_REDO ) );
i_undoManager.addUndoAction( new CustomUndoAction() );
i_undoManager.undo(); if ( !undo )
i_undoManager.undo(); // assert preconditions for the below test
assertTrue( i_undoManager.isUndoPossible() );
assertTrue( i_undoManager.isRedoPossible() );
boolean caughtUndoFailed = false; try
{ if ( undo ) if ( doByAPI )
i_undoManager.undo(); else
m_currentTestCase.getDocument().getCurrentView().dispatch( ".uno:Undo" ); else if ( doByAPI )
i_undoManager.redo(); else
m_currentTestCase.getDocument().getCurrentView().dispatch( ".uno:Redo" );
} catch ( UndoFailedException e )
{
caughtUndoFailed = true;
} if ( doByAPI )
assertTrue( "Exceptions in XUndoAction.undo should be propagated at the API", caughtUndoFailed ); else
assertFalse( "Undo/Redo by UI should not let escape Exceptions", caughtUndoFailed ); if ( undo )
{
assertFalse( "a failing Undo should clear the Undo stack", i_undoManager.isUndoPossible() );
assertTrue( "a failing Undo should /not/ clear the Redo stack", i_undoManager.isRedoPossible() );
} else
{
assertTrue( "a failing Redo should /not/ clear the Undo stack", i_undoManager.isUndoPossible() );
assertFalse( "a failing Redo should clear the Redo stack", i_undoManager.isRedoPossible() );
}
}
}
// add an action, clear the stack, verify the listener has been called
i_undoManager.addUndoAction( new CustomUndoAction() );
assertFalse( "clearance listener unexpectedly called", m_undoListener.wereStacksCleared() );
assertFalse( "redo-clearance listener unexpectedly called", m_undoListener.wasRedoStackCleared() );
i_undoManager.clear();
assertTrue( "clearance listener not called as expected", m_undoListener.wereStacksCleared() );
assertFalse( "redo-clearance listener unexpectedly called (2)", m_undoListener.wasRedoStackCleared() );
// ensure the listener is also called if the stack is actually empty at the moment of the call
m_undoListener.reset();
assertFalse( i_undoManager.isUndoPossible() );
i_undoManager.clear();
assertTrue( "clearance listener is also expected to be called if the stack was empty before", m_undoListener.wereStacksCleared() );
// ensure the proper listeners are called for clearRedo
m_undoListener.reset();
i_undoManager.clearRedo();
assertFalse( m_undoListener.wereStacksCleared() );
assertTrue( m_undoListener.wasRedoStackCleared() );
// ensure the redo listener is also called upon implicit redo stack clearance
m_undoListener.reset();
i_undoManager.addUndoAction( new CustomUndoAction() );
i_undoManager.addUndoAction( new CustomUndoAction() );
i_undoManager.undo();
assertTrue( i_undoManager.isUndoPossible() );
assertTrue( i_undoManager.isRedoPossible() );
i_undoManager.addUndoAction( new CustomUndoAction() );
assertFalse( i_undoManager.isRedoPossible() );
assertTrue( "implicit clearance of the Redo stack does not notify listeners", m_undoListener.wasRedoStackCleared() );
// test resetting the manager
m_undoListener.reset();
i_undoManager.addUndoAction( new CustomUndoAction() );
i_undoManager.addUndoAction( new CustomUndoAction() );
i_undoManager.undo();
assertTrue( i_undoManager.isUndoPossible() );
assertTrue( i_undoManager.isRedoPossible() );
i_undoManager.reset();
assertFalse( i_undoManager.isUndoPossible() );
assertFalse( i_undoManager.isRedoPossible() );
assertTrue( "|reset| does not properly notify", m_undoListener.wasManagerReset() );
// resetting the manager, with open undo contexts
m_undoListener.reset();
i_undoManager.addUndoAction( new CustomUndoAction() );
i_undoManager.enterUndoContext( "Undo Context" );
i_undoManager.addUndoAction( new CustomUndoAction() );
i_undoManager.enterHiddenUndoContext();
i_undoManager.reset();
assertTrue( "|reset| while contexts are open does not properly notify", m_undoListener.wasManagerReset() ); // verify the manager really has the proper context depth now
i_undoManager.enterUndoContext( "Undo Context" );
assertEquals( "seems that |reset| did not really close the open contexts", 1, m_undoListener.getCurrentUndoContextDepth() );
}
privatevoid impl_testHiddenContexts( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception
{
i_undoManager.reset();
m_undoListener.reset();
assertFalse( "precondition for testing hidden undo contexts not met", i_undoManager.isUndoPossible() );
// entering a hidden context should be rejected if the stack is empty boolean caughtExpected = false; try { i_undoManager.enterHiddenUndoContext(); } catch ( final EmptyUndoStackException e ) { caughtExpected = true; }
assertTrue( "entering hidden contexts should be denied on an empty stack", caughtExpected );
// but it should be allowed if the context is not empty final CustomUndoAction undoAction0 = new CustomUndoAction( "Step 0" );
i_undoManager.addUndoAction( undoAction0 ); final CustomUndoAction undoAction1 = new CustomUndoAction( "Step 1" );
i_undoManager.addUndoAction( undoAction1 );
i_undoManager.enterHiddenUndoContext(); final CustomUndoAction hiddenUndoAction = new CustomUndoAction( "hidden context action" );
i_undoManager.addUndoAction( hiddenUndoAction );
i_undoManager.leaveUndoContext();
assertFalse( "leaving a hidden should not call |leftUndocontext|", m_undoListener.wasContextLeft() );
assertTrue( "leaving a hidden does not call |leftHiddenUndocontext|", m_undoListener.wasHiddenContextLeft() );
assertFalse( "leaving a non-empty hidden context claims to have cancelled it", m_undoListener.hasContextBeenCancelled() );
assertEquals( "leaving a hidden context is not properly notified", 0, m_undoListener.getCurrentUndoContextDepth() );
assertArrayEquals( "unexpected Undo stack after leaving a hidden context", new String[] { undoAction1.getTitle(), undoAction0.getTitle() },
i_undoManager.getAllUndoActionTitles() );
// and then calling |undo| once should not only undo everything in the hidden context, but also // the previous action - but not more
i_undoManager.undo();
assertTrue( "Undo after leaving a hidden context does not actually undo the context actions",
hiddenUndoAction.undoCalled() );
assertTrue( "Undo after leaving a hidden context does not undo the predecessor action",
undoAction1.undoCalled() );
assertFalse( "Undo after leaving a hidden context undoes too much",
undoAction0.undoCalled() );
// leaving an empty hidden context should call the proper notification method
m_undoListener.reset();
i_undoManager.enterHiddenUndoContext();
i_undoManager.leaveUndoContext();
assertFalse( m_undoListener.wasContextLeft() );
assertFalse( m_undoListener.wasHiddenContextLeft() );
assertTrue( m_undoListener.hasContextBeenCancelled() );
// nesting hidden and normal contexts
m_undoListener.reset();
i_undoManager.reset(); final CustomUndoAction action0 = new CustomUndoAction( "action 0" );
i_undoManager.addUndoAction( action0 );
i_undoManager.enterUndoContext( "context 1" ); final CustomUndoAction action1 = new CustomUndoAction( "action 1" );
i_undoManager.addUndoAction( action1 );
i_undoManager.enterHiddenUndoContext(); final CustomUndoAction action2 = new CustomUndoAction( "action 2" );
i_undoManager.addUndoAction( action2 );
i_undoManager.enterUndoContext( "context 2" ); // is entering a hidden context rejected even at the nesting level > 0 (the above test was for nesting level == 0)?
caughtExpected = false; try { i_undoManager.enterHiddenUndoContext(); } catch( final EmptyUndoStackException e ) { caughtExpected = true; }
assertTrue( "at a nesting level > 0, denied hidden contexts does not work as expected", caughtExpected ); final CustomUndoAction action3 = new CustomUndoAction( "action 3" );
i_undoManager.addUndoAction( action3 );
i_undoManager.enterHiddenUndoContext();
assertEquals( "mixed hidden/normal context do are not properly notified", 4, m_undoListener.getCurrentUndoContextDepth() );
i_undoManager.leaveUndoContext();
assertTrue( "the left context was empty - why wasn't 'cancelled' notified?", m_undoListener.hasContextBeenCancelled() );
assertFalse( m_undoListener.wasContextLeft() );
assertFalse( m_undoListener.wasHiddenContextLeft() );
i_undoManager.leaveUndoContext();
i_undoManager.leaveUndoContext();
i_undoManager.leaveUndoContext();
i_undoManager.undo();
assertFalse( "one action too much has been undone", action0.undoCalled() );
assertTrue( action1.undoCalled() );
assertTrue( action2.undoCalled() );
assertTrue( action3.undoCalled() );
}
public String getTitle()
{ return"Counting Undo Action";
}
publicvoid undo() throws UndoFailedException
{ synchronized( m_lock )
{
assertEquals( "Undo action called out of order", m_expectedOrder, m_actionsUndoneCounter[0].intValue() );
++m_actionsUndoneCounter[0];
}
}
publicvoid redo() throws UndoFailedException
{
fail( "CountingUndoAction.redo is not expected to be called in this test." );
} privatefinalint m_expectedOrder; privatefinal Object m_lock; private Integer[] m_actionsUndoneCounter;
}
public String[] getSupportedServiceNames()
{ returnnew String[] { getCallbackComponentServiceName() };
}
@SuppressWarnings("unchecked") publicvoid dispose()
{ final EventObject event = new EventObject( this );
final ArrayList<XEventListener> eventListenersCopy = (ArrayList<XEventListener>)m_eventListeners.clone(); final Iterator<XEventListener> iter = eventListenersCopy.iterator(); while ( iter.hasNext() )
{
iter.next().disposing( event );
}
}
public Object execute( NamedValue[] i_parameters ) throws com.sun.star.lang.IllegalArgumentException, com.sun.star.uno.Exception
{ // this method is called from within the Basic script which is to check whether the OOo framework // properly cleans up unfinished Undo contexts. It is called immediately after the context has been // entered, so verify the expected Undo manager state.
assertEquals( getCallbackUndoContextTitle(), m_undoListener.getCurrentUndoContextTitle() );
assertEquals( 1, m_undoListener.getCurrentUndoContextDepth() );
public Type[] getTypes()
{ finalClass<?> interfaces[] = getClass().getInterfaces();
Type types[] = new Type[ interfaces.length ]; for ( int i = 0; i < interfaces.length; ++i )
types[i] = new Type(interfaces[i]); return types;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.