| /******************************************************************************* |
| * Copyright (c) 2005 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.ui.tests.operations; |
| |
| import junit.framework.TestCase; |
| |
| import org.eclipse.core.commands.ExecutionException; |
| import org.eclipse.core.commands.operations.DefaultOperationHistory; |
| import org.eclipse.core.commands.operations.ICompositeOperation; |
| import org.eclipse.core.commands.operations.IOperationHistoryListener; |
| import org.eclipse.core.commands.operations.IUndoableOperation; |
| import org.eclipse.core.commands.operations.IOperationApprover; |
| import org.eclipse.core.commands.operations.IOperationHistory; |
| import org.eclipse.core.commands.operations.LinearUndoEnforcer; |
| import org.eclipse.core.commands.operations.ObjectUndoContext; |
| import org.eclipse.core.commands.operations.OperationHistoryEvent; |
| import org.eclipse.core.commands.operations.OperationHistoryFactory; |
| import org.eclipse.core.commands.operations.OperationStatus; |
| import org.eclipse.core.commands.operations.TriggeredOperations; |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| |
| /** |
| * Tests the Operations Framework API. |
| * |
| * @since 3.1 |
| */ |
| public class OperationsAPITest extends TestCase { |
| |
| ObjectUndoContext contextA, contextB, contextC, contextW; |
| IOperationHistory history; |
| |
| IUndoableOperation op1, op2, op3, op4, op5, op6, localA, localB, localC; |
| ICompositeOperation refactor; |
| |
| int preExec, postExec, preUndo, postUndo, preRedo, postRedo, add, remove, notOK, changed = 0; |
| IOperationHistoryListener listener; |
| |
| public OperationsAPITest() { |
| super(); |
| } |
| |
| /** |
| * @param testName |
| */ |
| public OperationsAPITest(String name) { |
| super(name); |
| } |
| |
| protected void setUp() throws Exception { |
| history = new DefaultOperationHistory(); |
| contextA = new ObjectUndoContext("A"); |
| contextB = new ObjectUndoContext("B"); |
| contextC = new ObjectUndoContext("C"); |
| op1 = new TestOperation("op1"); |
| op1.addContext(contextA); |
| op2 = new TestOperation("op2"); |
| op2.addContext(contextB); |
| op2.addContext(contextC); |
| op3 = new TestOperation("op3"); |
| op3.addContext(contextC); |
| op4 = new TestOperation("op4"); |
| op4.addContext(contextA); |
| op5 = new TestOperation("op5"); |
| op5.addContext(contextB); |
| op6 = new TestOperation("op6"); |
| op6.addContext(contextC); |
| op6.addContext(contextA); |
| history.execute(op1, null, null); |
| history.execute(op2, null, null); |
| history.execute(op3, null, null); |
| history.execute(op4, null, null); |
| history.execute(op5, null, null); |
| history.execute(op6, null, null); |
| preExec = 0; postExec = 0; |
| preUndo = 0; postUndo = 0; |
| preRedo = 0; postRedo = 0; |
| add = 0; remove = 0; notOK = 0; |
| listener = new IOperationHistoryListener() { |
| public void historyNotification(OperationHistoryEvent event) { |
| switch (event.getEventType()) { |
| case OperationHistoryEvent.ABOUT_TO_EXECUTE: |
| preExec++; |
| break; |
| case OperationHistoryEvent.ABOUT_TO_UNDO: |
| preUndo++; |
| break; |
| case OperationHistoryEvent.ABOUT_TO_REDO: |
| preRedo++; |
| break; |
| case OperationHistoryEvent.DONE: |
| postExec++; |
| break; |
| case OperationHistoryEvent.UNDONE: |
| postUndo++; |
| break; |
| case OperationHistoryEvent.REDONE: |
| postRedo++; |
| break; |
| case OperationHistoryEvent.OPERATION_ADDED: |
| add++; |
| break; |
| case OperationHistoryEvent.OPERATION_REMOVED: |
| remove++; |
| break; |
| case OperationHistoryEvent.OPERATION_NOT_OK: |
| notOK++; |
| break; |
| case OperationHistoryEvent.OPERATION_CHANGED: |
| changed++; |
| break; |
| } |
| } |
| }; |
| history.addOperationHistoryListener(listener); |
| |
| } |
| |
| protected void tearDown() throws Exception { |
| super.tearDown(); |
| history.removeOperationHistoryListener(listener); |
| history.dispose(IOperationHistory.GLOBAL_UNDO_CONTEXT, true, true, false); |
| } |
| |
| public void testContextDispose() throws ExecutionException { |
| assertSame(history.getUndoOperation(contextA), op6); |
| assertSame(history.getUndoOperation(contextC), op6); |
| history.dispose(contextA, true, true, false); |
| assertSame(history.getUndoOperation(contextC), op6); |
| assertFalse(op6.hasContext(contextA)); |
| history.undo(contextC, null, null); |
| history.dispose(contextC, true, false, false); |
| assertFalse(history.canUndo(contextC)); |
| assertTrue(history.canRedo(contextC)); |
| history.redo(contextC, null, null); |
| IUndoableOperation[] ops = history.getUndoHistory(IOperationHistory.GLOBAL_UNDO_CONTEXT); |
| assertEquals(ops.length, 3); |
| ops = history.getUndoHistory(contextC); |
| assertEquals(ops.length, 1); |
| ops = history.getUndoHistory(contextB); |
| assertEquals(ops.length, 2); |
| } |
| |
| public void testContextHistories() throws ExecutionException { |
| assertSame(history.getUndoOperation(contextA), op6); |
| assertSame(history.getUndoOperation(contextB), op5); |
| assertSame(history.getUndoOperation(contextC), op6); |
| IStatus status = history.undo(contextC, null, null); |
| assertTrue("Status should be ok", status.isOK()); |
| assertSame(history.getRedoOperation(contextC), op6); |
| assertSame(history.getUndoOperation(contextC), op3); |
| assertTrue("Should be able to redo in c3", history.canRedo(contextC)); |
| assertTrue("Should be able to redo in c1", history.canRedo(contextA)); |
| history.redo(contextA, null, null); |
| assertSame(history.getUndoOperation(contextC), op6); |
| assertSame(history.getUndoOperation(contextA), op6); |
| } |
| |
| public void testHistoryLimit() throws ExecutionException { |
| history.setLimit(contextA, 2); |
| assertTrue(history.getUndoHistory(contextA).length == 2); |
| history.add(op1); |
| assertTrue(history.getUndoHistory(contextA).length == 2); |
| history.setLimit(contextB, 1); |
| assertTrue(history.getUndoHistory(contextB).length == 1); |
| assertFalse(op2.hasContext(contextB)); |
| history.undo(contextB, null, null); |
| assertTrue(history.getRedoHistory(contextB).length == 1); |
| assertTrue(history.getUndoHistory(contextB).length == 0); |
| history.redo(contextB, null, null); |
| assertTrue(history.getRedoHistory(contextB).length == 0); |
| assertTrue(history.getUndoHistory(contextB).length == 1); |
| } |
| |
| public void testLocalHistoryLimits() throws ExecutionException { |
| history.setLimit(contextC, 2); |
| assertTrue(history.getUndoHistory(contextC).length == 2); |
| // op2 should have context c3 removed as part of forcing the limit |
| assertFalse(op2.hasContext(contextC)); |
| assertTrue(history.getUndoHistory(contextB).length == 2); |
| |
| history.setLimit(contextB, 1); |
| assertTrue(history.getUndoHistory(contextB).length == 1); |
| history.undo(contextB, null, null); |
| op2.addContext(contextC); |
| history.add(op2); |
| assertSame(history.getUndoOperation(contextB), op2); |
| assertTrue(history.getUndoHistory(contextB).length == 1); |
| |
| history.setLimit(contextA, 0); |
| assertTrue(history.getUndoHistory(contextA).length == 0); |
| history.add(op1); |
| assertTrue(history.getUndoHistory(contextA).length == 0); |
| } |
| |
| public void testOpenOperation() throws ExecutionException { |
| // clear out history which will also reset operation execution counts |
| history.dispose(IOperationHistory.GLOBAL_UNDO_CONTEXT, true, true, false); |
| ICompositeOperation batch = new TriggeredOperations(op1, history); |
| history.openOperation(batch, IOperationHistory.EXECUTE); |
| op1.execute(null, null); |
| op2.execute(null, null); |
| history.add(op2); |
| history.execute(op3, null, null); |
| IUndoableOperation op = history.getUndoOperation(IOperationHistory.GLOBAL_UNDO_CONTEXT); |
| assertTrue("no operations should be in history yet", op == null); |
| history.closeOperation(true, true, IOperationHistory.EXECUTE); |
| op = history.getUndoOperation(IOperationHistory.GLOBAL_UNDO_CONTEXT); |
| assertTrue("Operation should be batching", op == batch); |
| op.removeContext(contextB); |
| assertFalse("Operation should not have context", op.hasContext(contextB)); |
| } |
| |
| public void test94459() throws ExecutionException { |
| // clear out history which will also reset operation execution counts |
| history.dispose(IOperationHistory.GLOBAL_UNDO_CONTEXT, true, true, false); |
| op2.execute(null, null); |
| ICompositeOperation batch = new TriggeredOperations(op2, history); |
| history.openOperation(batch, IOperationHistory.EXECUTE); |
| history.setLimit(contextA, 0); |
| op1.execute(null, null); |
| history.add(op1); |
| history.closeOperation(true, true, IOperationHistory.EXECUTE); |
| IUndoableOperation op = history.getUndoOperation(IOperationHistory.GLOBAL_UNDO_CONTEXT); |
| assertTrue("Operation should be batching", op == batch); |
| assertFalse("Operation should not have context", op.hasContext(contextA)); |
| } |
| |
| public void test94459AllContextsEmpty() throws ExecutionException { |
| // clear out history which will also reset operation execution counts |
| history.dispose(IOperationHistory.GLOBAL_UNDO_CONTEXT, true, true, false); |
| op2.execute(null, null); |
| ICompositeOperation batch = new TriggeredOperations(op2, history); |
| history.openOperation(batch, IOperationHistory.EXECUTE); |
| history.setLimit(contextA, 0); |
| history.setLimit(contextB, 0); |
| history.setLimit(contextC, 0); |
| op1.execute(null, null); |
| history.add(op1); |
| history.closeOperation(true, true, IOperationHistory.EXECUTE); |
| IUndoableOperation op = history.getUndoOperation(IOperationHistory.GLOBAL_UNDO_CONTEXT); |
| assertTrue("Operation should not have been added", op == null); |
| } |
| |
| public void test94400() throws ExecutionException { |
| UnredoableTestOperation op = new UnredoableTestOperation("troubled op"); |
| op.addContext(contextA); |
| history.execute(op, null, null); |
| assertTrue("Operation should be undoable", history.canUndo(contextA)); |
| history.undo(contextA, null, null); |
| assertFalse("Operation should not be in redo history", history.getRedoOperation(contextA) == op); |
| assertTrue("Operation should be disposed", op.disposed); |
| } |
| |
| public void testUnsuccessfulOpenOperation() throws ExecutionException { |
| // clear out history which will also reset operation execution counts |
| history.dispose(IOperationHistory.GLOBAL_UNDO_CONTEXT, true, true, false); |
| ICompositeOperation batch = new TriggeredOperations(op1, history); |
| history.openOperation(batch, IOperationHistory.EXECUTE); |
| op1.execute(null, null); |
| op2.execute(null, null); |
| history.add(op2); |
| history.execute(op3, null, null); |
| IUndoableOperation op = history.getUndoOperation(IOperationHistory.GLOBAL_UNDO_CONTEXT); |
| assertTrue("no operations should be in history yet", op == null); |
| history.closeOperation(false, true, IOperationHistory.EXECUTE); |
| op = history.getUndoOperation(IOperationHistory.GLOBAL_UNDO_CONTEXT); |
| assertNull("Unsuccessful operation should not be added to history", op); |
| assertTrue("NOT_OK notification should have been received", notOK == 1); |
| assertTrue("DONE should not be sent while batching", postExec == 0); |
| assertTrue("ADDED should not have been sent while batching", add == 0); |
| } |
| |
| public void testNotAddedOpenOperation() throws ExecutionException { |
| // clear out history which will also reset operation execution counts |
| history.dispose(IOperationHistory.GLOBAL_UNDO_CONTEXT, true, true, false); |
| ICompositeOperation batch = new TriggeredOperations(op1, history); |
| history.openOperation(batch, IOperationHistory.EXECUTE); |
| op1.execute(null, null); |
| op2.execute(null, null); |
| history.add(op2); |
| history.execute(op3, null, null); |
| IUndoableOperation op = history.getUndoOperation(IOperationHistory.GLOBAL_UNDO_CONTEXT); |
| assertTrue("no operations should be in history yet", op == null); |
| history.closeOperation(true, false, IOperationHistory.EXECUTE); |
| op = history.getUndoOperation(IOperationHistory.GLOBAL_UNDO_CONTEXT); |
| assertNull("Operation should not be added to history", op); |
| assertTrue("DONE notification should have been received", postExec == 1); |
| assertTrue("ADDED should not have occurred or be sent while batching", add == 0); |
| } |
| |
| public void testMultipleOpenOperation() throws ExecutionException { |
| // clear out history which will also reset operation execution counts |
| boolean failure = false; |
| history.dispose(IOperationHistory.GLOBAL_UNDO_CONTEXT, true, true, false); |
| ICompositeOperation comp1 = new TriggeredOperations(op1, history); |
| history.openOperation(comp1, IOperationHistory.EXECUTE); |
| op1.execute(null, null); |
| op2.execute(null, null); |
| history.add(op2); |
| history.execute(op3, null, null); |
| ICompositeOperation comp2 = new TriggeredOperations(op4, history); |
| try { |
| history.openOperation(comp2, IOperationHistory.EXECUTE); |
| } catch (IllegalStateException e) { |
| failure = true; |
| } |
| assertTrue("Exception should have been thrown for second open operation", failure); |
| IUndoableOperation op = history.getUndoOperation(IOperationHistory.GLOBAL_UNDO_CONTEXT); |
| assertNull("Unexpected nested open should not add original", op); |
| history.closeOperation(true, true, IOperationHistory.EXECUTE); |
| op = history.getUndoOperation(IOperationHistory.GLOBAL_UNDO_CONTEXT); |
| assertSame("First operation should be closed", op, comp1); |
| } |
| |
| public void testAbortedOpenOperation() throws ExecutionException { |
| history.dispose(IOperationHistory.GLOBAL_UNDO_CONTEXT, true, true, false); |
| history.openOperation(new TriggeredOperations(op1, history), IOperationHistory.EXECUTE); |
| op1.execute(null, null); |
| history.execute(op2, null, null); |
| // flush history while operation is open |
| history.dispose(IOperationHistory.GLOBAL_UNDO_CONTEXT, true, true, false); |
| // op3 should be added as its own op since we flushed while open |
| history.add(op3); |
| // should really have no effect |
| history.closeOperation(true, true, IOperationHistory.EXECUTE); |
| IUndoableOperation op = history.getUndoOperation(IOperationHistory.GLOBAL_UNDO_CONTEXT); |
| assertTrue("Open operation should be flushed", op == op3); |
| } |
| |
| public void testOperationApproval() throws ExecutionException { |
| history.addOperationApprover(new LinearUndoEnforcer()); |
| // the first undo should be fine |
| IStatus status = history.undo(contextB, null, null); |
| assertTrue(status.isOK()); |
| |
| // the second causes a linear violation on C |
| assertTrue(history.canUndo(contextB)); |
| status = history.undo(contextB, null, null); |
| assertFalse(status.isOK()); |
| |
| // undo the newer C items |
| status = history.undo(contextC, null, null); |
| assertTrue(status.isOK()); |
| status = history.undo(contextC, null, null); |
| assertTrue(status.isOK()); |
| |
| // now we should be okay in B |
| status = history.undo(contextB, null, null); |
| assertTrue(status.isOK()); |
| |
| history.addOperationApprover(new IOperationApprover() { |
| |
| public IStatus proceedRedoing(IUndoableOperation o, IOperationHistory h, IAdaptable a) { |
| return Status.CANCEL_STATUS; |
| } |
| public IStatus proceedUndoing(IUndoableOperation o, IOperationHistory h, IAdaptable a) { |
| return Status.CANCEL_STATUS; |
| } |
| }); |
| // everything should fail now |
| assertFalse(history.redo(contextB, null, null).isOK()); |
| assertFalse(history.redo(contextC, null, null).isOK()); |
| assertFalse(history.undo(contextA, null, null).isOK()); |
| assertFalse(history.undo(contextB, null, null).isOK()); |
| assertFalse(history.undo(contextC, null, null).isOK()); |
| } |
| |
| public void testOperationFailure() throws ExecutionException { |
| history.addOperationApprover(new IOperationApprover() { |
| |
| public IStatus proceedRedoing(IUndoableOperation o, IOperationHistory h, IAdaptable a) { |
| return Status.OK_STATUS; |
| } |
| public IStatus proceedUndoing(IUndoableOperation o, IOperationHistory h, IAdaptable a) { |
| if (o == op6) |
| return Status.CANCEL_STATUS; |
| if (o == op5) |
| return new OperationStatus(IStatus.ERROR, "org.eclipse.ui.tests", 0, "Error", null); |
| return Status.OK_STATUS; |
| } |
| }); |
| |
| // should fail but still keep op6 on the stack since it's cancelled |
| IStatus status = history.undo(contextC, null, null); |
| assertFalse(status.isOK()); |
| assertSame(history.getUndoOperation(contextC), op6); |
| |
| // should fail since it's an error |
| status = history.undo(contextB, null, null); |
| assertFalse(status.isOK()); |
| |
| // operation remains on stack (see bug#92506) |
| assertSame(history.getUndoOperation(contextB), op5); |
| } |
| |
| public void testOperationRedo() throws ExecutionException { |
| history.undo(contextB, null, null); |
| history.undo(contextB, null, null); |
| history.undo(contextC, null, null); |
| history.undo(contextC, null, null); |
| assertSame(history.getRedoOperation(contextB), op2); |
| assertSame(history.getUndoOperation(contextA), op4); |
| assertTrue(history.canUndo(contextA)); |
| assertFalse(history.canUndo(contextB)); |
| assertFalse(history.canUndo(contextC)); |
| assertTrue(preUndo == 4); |
| assertTrue(postUndo == 4); |
| history.redo(contextB, null, null); |
| assertTrue(postRedo == 1); |
| assertTrue(history.canUndo(contextB)); |
| assertTrue(history.canUndo(contextC)); |
| } |
| |
| public void testOperationUndo() throws ExecutionException { |
| history.undo(contextA, null, null); |
| history.undo(contextA, null, null); |
| assertSame(history.getRedoOperation(contextA), op4); |
| assertSame(history.getUndoOperation(contextA), op1); |
| history.undo(contextA, null, null); |
| assertTrue(preUndo == 3); |
| assertTrue(postUndo == 3); |
| assertFalse("Shouldn't be able to undo in c1", history.canUndo(contextA)); |
| assertTrue("Should be able to undo in c2", history.canUndo(contextB)); |
| assertTrue("Should be able to undo in c3", history.canUndo(contextC)); |
| } |
| |
| public void testHistoryFactory() { |
| IOperationHistory anotherHistory = OperationHistoryFactory.getOperationHistory(); |
| assertNotNull(anotherHistory); |
| } |
| |
| public void testOperationChanged() { |
| history.operationChanged(op1); |
| history.operationChanged(op2); |
| history.operationChanged(new TestOperation("New op")); |
| assertTrue("should not notify about changes if not in the history", changed == 2); |
| } |
| |
| // the setup for the infamous (local conflict on top of composite and composite gets pruned) case |
| private void setup87675() throws ExecutionException { |
| // clear everything out. special setup for this test case |
| history.dispose(IOperationHistory.GLOBAL_UNDO_CONTEXT, true, true, false); |
| contextA = new ObjectUndoContext("A"); |
| contextB = new ObjectUndoContext("B"); |
| contextC = new ObjectUndoContext("C"); |
| contextW = new ObjectUndoContext("W"); |
| history.addOperationApprover(new LinearUndoEnforcer()); |
| |
| // local edits on A, B, C are added first |
| IUndoableOperation op = new TestOperation("op1a"); |
| op.addContext(contextA); |
| history.execute(op, null, null); |
| op = new TestOperation("op1b"); |
| op.addContext(contextB); |
| history.execute(op, null, null); |
| op = new TestOperation("op1c"); |
| op.addContext(contextC); |
| history.execute(op, null, null); |
| |
| // now we create the "refactoring op" which touches them all |
| op = new TestOperation("Refactoring"); |
| op.addContext(contextW); |
| op.execute(null, null); |
| refactor = new TriggeredOperations(op, history); |
| history.openOperation(refactor, IOperationHistory.EXECUTE); |
| localA = new TestOperation("op2a"); |
| localA.addContext(contextA); |
| history.execute(localA, null, null); |
| localB = new TestOperation("op2b"); |
| localB.addContext(contextB); |
| history.execute(localB, null, null); |
| localC = new TestOperation("op2c"); |
| localC.addContext(contextC); |
| history.execute(localC, null, null); |
| |
| // close off the composite |
| history.closeOperation(true, true, IOperationHistory.EXECUTE); |
| |
| // subsequent local edit to C |
| op = new TestOperation("op3c"); |
| op.addContext(contextC); |
| history.execute(op, null, null); |
| } |
| |
| public void test87675_split() throws ExecutionException { |
| setup87675(); |
| IUndoableOperation op; |
| |
| // check setup |
| op = history.getUndoOperation(contextA); |
| assertTrue("Refactoring should be next op for context A", op == refactor); |
| op = history.getUndoOperation(contextB); |
| assertTrue("Refactoring should be next op for context B", op == refactor); |
| op = history.getUndoOperation(contextW); |
| assertTrue("Refactoring should be next op for context W", op == refactor); |
| op = history.getUndoOperation(contextC); |
| assertFalse("Refactoring should not be next op for context C", op == refactor); |
| |
| // try a bogus undo |
| IStatus status = history.undo(contextW, null, null); |
| assertFalse("Undo should not be permitted due to linear conflict", status.isOK()); |
| |
| // prune the history for contextW |
| history.dispose(contextW, true, true, false); |
| |
| // refactoring op should have been broken up into pieces |
| op = history.getUndoOperation(contextA); |
| assertTrue("Local edit A should be atomic", op == localA); |
| op = history.getUndoOperation(contextB); |
| assertTrue("Local edit B should be atomic", op == localB); |
| op = history.getUndoOperation(contextC); |
| assertFalse("Local edit C should not be refactoring edit", op == localC); |
| |
| // now the refactoring C edit should be the next one |
| history.undo(contextC, null, null); |
| op = history.getUndoOperation(contextC); |
| assertTrue("Local edit C should be refactoring edit", op == localC); |
| } |
| |
| public void test87675_undoredo() throws ExecutionException { |
| setup87675(); |
| IUndoableOperation op; |
| |
| // undo the local edit to C |
| history.undo(contextC, null, null); |
| |
| // undo the refactoring operation via context C |
| history.undo(contextC, null, null); |
| |
| // check that there are no new operations in the undo list for A, B, C |
| op = history.getUndoOperation(contextC); |
| assertTrue("Local edit C should be original edit", op.getLabel().equals("op1c")); |
| |
| op = history.getUndoOperation(contextB); |
| assertTrue("Local edit B should be original edit", op.getLabel().equals("op1b")); |
| |
| op = history.getUndoOperation(contextA); |
| assertTrue("Local edit A should be original edit", op.getLabel().equals("op1a")); |
| |
| // test that the redo operation has all contexts |
| op = history.getRedoOperation(contextW); |
| assertTrue("operation should have context A", op.hasContext(contextA)); |
| assertTrue("operation should have context B", op.hasContext(contextB)); |
| assertTrue("operation should have context C", op.hasContext(contextC)); |
| |
| // now redo the operation |
| history.redo(contextA, null, null); |
| |
| // test that the next undo is our refactoring operation |
| op = history.getUndoOperation(IOperationHistory.GLOBAL_UNDO_CONTEXT); |
| assertTrue("operation should have context W", op.hasContext(contextW)); |
| |
| // undo again and check that no side effect ops were left on the undo stack |
| history.undo(contextW, null, null); |
| |
| op = history.getUndoOperation(contextC); |
| assertTrue("Local edit C should be original edit", op.getLabel().equals("op1c")); |
| |
| op = history.getUndoOperation(contextB); |
| assertTrue("Local edit B should be original edit", op.getLabel().equals("op1b")); |
| |
| op = history.getUndoOperation(contextA); |
| assertTrue("Local edit A should be original edit", op.getLabel().equals("op1a")); |
| |
| } |
| } |