Merge branch 'bug/JAL-2541cutRelocateFeatures' into develop
[jalview.git] / test / jalview / commands / EditCommandTest.java
index 155f00e..2160657 100644 (file)
@@ -21,8 +21,8 @@
 package jalview.commands;
 
 import static org.testng.AssertJUnit.assertEquals;
-import static org.testng.AssertJUnit.assertNull;
 import static org.testng.AssertJUnit.assertSame;
+import static org.testng.AssertJUnit.assertTrue;
 
 import jalview.commands.EditCommand.Action;
 import jalview.commands.EditCommand.Edit;
@@ -34,6 +34,8 @@ import jalview.datamodel.SequenceI;
 import jalview.datamodel.features.SequenceFeatures;
 import jalview.gui.JvOptionPane;
 
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 
@@ -50,6 +52,22 @@ import org.testng.annotations.Test;
  */
 public class EditCommandTest
 {
+  private static Comparator<SequenceFeature> BY_DESCRIPTION = new Comparator<SequenceFeature>()
+  {
+
+    @Override
+    public int compare(SequenceFeature o1, SequenceFeature o2)
+    {
+      return o1.getDescription().compareTo(o2.getDescription());
+    }
+  };
+
+  private EditCommand testee;
+
+  private SequenceI[] seqs;
+
+  private Alignment al;
+
   /*
    * compute n(n+1)/2 e.g. 
    * func(5) = 5 + 4 + 3 + 2 + 1 = 15
@@ -66,12 +84,6 @@ public class EditCommandTest
     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
   }
 
-  private EditCommand testee;
-
-  private SequenceI[] seqs;
-
-  private Alignment al;
-
   @BeforeMethod(alwaysRun = true)
   public void setUp()
   {
@@ -141,18 +153,23 @@ public class EditCommandTest
   }
 
   /**
-   * Test a Paste action, where this adds sequences to an alignment.
+   * Test a Paste action, followed by Undo and Redo
    */
   @Test(groups = { "Functional" }, enabled = false)
-  // TODO fix so it works
-  public void testPaste_addToAlignment()
+  public void testPaste_undo_redo()
   {
+    // TODO code this test properly, bearing in mind that:
+    // Paste action requires something on the clipboard (Cut/Copy)
+    // - EditCommand.paste doesn't add sequences to the alignment
+    // ... that is done in AlignFrame.paste()
+    // ... unless as a Redo
+    // ...
+
     SequenceI[] newSeqs = new SequenceI[2];
     newSeqs[0] = new Sequence("newseq0", "ACEFKL");
     newSeqs[1] = new Sequence("newseq1", "JWMPDH");
 
-    Edit ec = testee.new Edit(Action.PASTE, newSeqs, 0, al.getWidth(), al);
-    EditCommand.paste(ec, new AlignmentI[] { al });
+    new EditCommand("Paste", Action.PASTE, newSeqs, 0, al.getWidth(), al);
     assertEquals(6, al.getSequences().size());
     assertEquals("1234567890", seqs[3].getSequenceAsString());
     assertEquals("ACEFKL", seqs[4].getSequenceAsString());
@@ -262,12 +279,123 @@ public class EditCommandTest
   {
     // seem to need a dataset sequence on the edited sequence here
     seqs[1].createDatasetSequence();
-    new EditCommand("", Action.REPLACE, "ZXY", new SequenceI[] { seqs[1] },
+    assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
+    // NB command.number holds end position for a Replace command
+    new EditCommand("", Action.REPLACE, "Z-xY", new SequenceI[] { seqs[1] },
             4, 8, al);
     assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
+    assertEquals("fghjZ-xYopq", seqs[1].getSequenceAsString());
+    assertEquals("fghjZxYopq",
+            seqs[1].getDatasetSequence().getSequenceAsString());
     assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
     assertEquals("1234567890", seqs[3].getSequenceAsString());
-    seqs[1] = new Sequence("seq1", "fghjZXYnopq");
+  }
+
+  /**
+   * Test the replace command (used to manually edit a sequence)
+   */
+  @Test(groups = { "Functional" })
+  public void testReplace_withGaps()
+  {
+    SequenceI seq = new Sequence("seq", "ABC--DEF");
+    seq.createDatasetSequence();
+    assertEquals("ABCDEF", seq.getDatasetSequence().getSequenceAsString());
+    assertEquals(1, seq.getStart());
+    assertEquals(6, seq.getEnd());
+
+    /*
+     * replace C- with XYZ
+     * NB arg4 = start column of selection for edit (base 0)
+     * arg5 = column after end of selection for edit
+     */
+    EditCommand edit = new EditCommand("", Action.REPLACE, "xyZ",
+            new SequenceI[]
+            { seq }, 2,
+            4, al);
+    assertEquals("ABxyZ-DEF", seq.getSequenceAsString());
+    assertEquals(1, seq.getStart());
+    assertEquals(8, seq.getEnd());
+    assertEquals("ABxyZDEF",
+            seq.getDatasetSequence().getSequenceAsString());
+    assertEquals(8, seq.getDatasetSequence().getEnd());
+
+    /*
+     * undo the edit
+     */
+    AlignmentI[] views = new AlignmentI[]
+    { new Alignment(new SequenceI[] { seq }) };
+    edit.undoCommand(views);
+
+    assertEquals("ABC--DEF", seq.getSequenceAsString());
+    assertEquals("ABCDEF", seq.getDatasetSequence().getSequenceAsString());
+    assertEquals(1, seq.getStart());
+    assertEquals(6, seq.getEnd());
+    assertEquals(6, seq.getDatasetSequence().getEnd());
+
+    /*
+     * redo the edit
+     */
+    edit.doCommand(views);
+
+    assertEquals("ABxyZ-DEF", seq.getSequenceAsString());
+    assertEquals(1, seq.getStart());
+    assertEquals(8, seq.getEnd());
+    assertEquals("ABxyZDEF",
+            seq.getDatasetSequence().getSequenceAsString());
+    assertEquals(8, seq.getDatasetSequence().getEnd());
+
+  }
+
+  /**
+   * Test replace command when it doesn't cause a sequence edit (see comment in
+   */
+  @Test(groups = { "Functional" })
+  public void testReplaceFirstResiduesWithGaps()
+  {
+    // test replace when gaps are inserted at start. Start/end should change
+    // w.r.t. original edited sequence.
+    SequenceI dsseq = seqs[1].getDatasetSequence();
+    EditCommand edit = new EditCommand("", Action.REPLACE, "----",
+            new SequenceI[]
+            { seqs[1] }, 0, 4, al);
+
+    // trimmed start
+    assertEquals("----klmnopq", seqs[1].getSequenceAsString());
+    // and ds is preserved
+    assertTrue(dsseq == seqs[1].getDatasetSequence());
+    // and it is unchanged
+    assertEquals("fghjklmnopq", dsseq.getSequenceAsString());
+    // and that alignment sequence start has been adjusted
+    assertEquals(5, seqs[1].getStart());
+    assertEquals(11, seqs[1].getEnd());
+
+    AlignmentI[] views = new AlignmentI[] { new Alignment(seqs) };
+    // and undo
+    edit.undoCommand(views);
+
+    // dataset sequence unchanged
+    assertTrue(dsseq == seqs[1].getDatasetSequence());
+    // restore sequence
+    assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
+    // and start/end numbering also restored
+    assertEquals(1, seqs[1].getStart());
+    assertEquals(11, seqs[1].getEnd());
+
+    // now redo
+    edit.undoCommand(views);
+
+    // and repeat asserts for the original edit
+
+    // trimmed start
+    assertEquals("----klmnopq", seqs[1].getSequenceAsString());
+    // and ds is preserved
+    assertTrue(dsseq == seqs[1].getDatasetSequence());
+    // and it is unchanged
+    assertEquals("fghjklmnopq", dsseq.getSequenceAsString());
+    // and that alignment sequence start has been adjusted
+    assertEquals(5, seqs[1].getStart());
+    assertEquals(11, seqs[1].getEnd());
+
   }
 
   /**
@@ -663,7 +791,7 @@ public class EditCommandTest
      * create sequence features before, after and overlapping
      * a cut of columns/residues 4-7
      */
-    SequenceI seq0 = seqs[0];
+    SequenceI seq0 = seqs[0]; // abcdefghjk/1-10
     seq0.addSequenceFeature(new SequenceFeature("before", "", 1, 3, 0f,
             null));
     seq0.addSequenceFeature(new SequenceFeature("overlap left", "", 2, 6,
@@ -675,29 +803,52 @@ public class EditCommandTest
     seq0.addSequenceFeature(new SequenceFeature("after", "", 8, 10, 0f,
             null));
 
+    /*
+     * add some contact features
+     */
+    SequenceFeature internalContact = new SequenceFeature("disulphide bond", "", 5,
+            6, 0f, null);
+    seq0.addSequenceFeature(internalContact); // should get deleted
+    SequenceFeature overlapLeftContact = new SequenceFeature(
+            "disulphide bond", "", 2, 6, 0f, null);
+    seq0.addSequenceFeature(overlapLeftContact); // should get deleted
+    SequenceFeature overlapRightContact = new SequenceFeature(
+            "disulphide bond", "", 5, 8, 0f, null);
+    seq0.addSequenceFeature(overlapRightContact); // should get deleted
+    SequenceFeature spanningContact = new SequenceFeature(
+            "disulphide bond", "", 2, 9, 0f, null);
+    seq0.addSequenceFeature(spanningContact); // should get shortened 3'
+
+    /*
+     * cut columns 3-6 (base 0), residues d-g 4-7
+     */
     Edit ec = testee.new Edit(Action.CUT, seqs, 3, 4, al); // cols 3-6 base 0
     EditCommand.cut(ec, new AlignmentI[] { al });
 
     List<SequenceFeature> sfs = seq0.getSequenceFeatures();
     SequenceFeatures.sortFeatures(sfs, true);
 
-    assertEquals(4, sfs.size()); // feature internal to cut has been deleted
+    assertEquals(5, sfs.size()); // features internal to cut were deleted
     SequenceFeature sf = sfs.get(0);
     assertEquals("before", sf.getType());
     assertEquals(1, sf.getBegin());
     assertEquals(3, sf.getEnd());
     sf = sfs.get(1);
+    assertEquals("disulphide bond", sf.getType());
+    assertEquals(2, sf.getBegin());
+    assertEquals(5, sf.getEnd()); // truncated by cut
+    sf = sfs.get(2);
     assertEquals("overlap left", sf.getType());
     assertEquals(2, sf.getBegin());
     assertEquals(3, sf.getEnd()); // truncated by cut
-    sf = sfs.get(2);
-    assertEquals("overlap right", sf.getType());
-    assertEquals(4, sf.getBegin()); // shifted left by cut
-    assertEquals(5, sf.getEnd()); // truncated by cut
     sf = sfs.get(3);
     assertEquals("after", sf.getType());
     assertEquals(4, sf.getBegin()); // shifted left by cut
     assertEquals(6, sf.getEnd()); // shifted left by cut
+    sf = sfs.get(4);
+    assertEquals("overlap right", sf.getType());
+    assertEquals(4, sf.getBegin()); // shifted left by cut
+    assertEquals(4, sf.getEnd()); // truncated by cut
   }
 
   /**
@@ -711,16 +862,28 @@ public class EditCommandTest
      * create a sequence features on each subrange of 1-5
      */
     SequenceI seq0 = new Sequence("seq", "ABCDE");
+    int start = 8;
+    int end = 12;
+    seq0.setStart(start);
+    seq0.setEnd(end);
     AlignmentI alignment = new Alignment(new SequenceI[] { seq0 });
     alignment.setDataset(null);
-    for (int from = 1; from <= seq0.getLength(); from++)
+
+    /*
+     * create a new alignment with shared dataset sequence
+     */
+    AlignmentI copy = new Alignment(
+            new SequenceI[]
+            { alignment.getDataset().getSequenceAt(0).deriveSequence() });
+    SequenceI copySeq0 = copy.getSequenceAt(0);
+
+    for (int from = start; from <= end; from++)
     {
-      for (int to = from; to <= seq0.getLength(); to++)
+      for (int to = from; to <= end; to++)
       {
         String desc = String.format("%d-%d", from, to);
         SequenceFeature sf = new SequenceFeature("test", desc, from, to,
-                0f,
-                null);
+                0f, null);
         sf.setValue("from", Integer.valueOf(from));
         sf.setValue("to", Integer.valueOf(to));
         seq0.addSequenceFeature(sf);
@@ -729,109 +892,234 @@ public class EditCommandTest
     // sanity check
     List<SequenceFeature> sfs = seq0.getSequenceFeatures();
     assertEquals(func(5), sfs.size());
+    assertEquals(sfs, copySeq0.getSequenceFeatures());
+    String copySequenceFeatures = copySeq0.getSequenceFeatures().toString();
 
     /*
-     * now perform all possible cuts of subranges of 1-5 (followed by Undo)
+     * now perform all possible cuts of subranges of columns 1-5
      * and validate the resulting remaining sequence features!
      */
     SequenceI[] sqs = new SequenceI[] { seq0 };
 
-    // goal is to have this passing for all from/to values!!
-    // for (int from = 0; from < seq0.getLength(); from++)
-    // {
-    // for (int to = from; to < seq0.getLength(); to++)
-    for (int from = 1; from < 3; from++)
+    for (int from = 0; from < seq0.getLength(); from++)
     {
-      for (int to = 2; to < 3; to++)
+      for (int to = from; to < seq0.getLength(); to++)
       {
-        testee.appendEdit(Action.CUT, sqs, from, (to - from + 1),
-                alignment, true);
+        EditCommand ec = new EditCommand("Cut", Action.CUT, sqs, from, (to
+                - from + 1), alignment);
+        final String msg = String.format("Cut %d-%d ", from + 1, to + 1);
+        boolean newDatasetSequence = copySeq0.getDatasetSequence() != seq0
+                .getDatasetSequence();
+
+        verifyCut(seq0, from, to, msg, start);
+
+        /*
+         * verify copy alignment dataset sequence unaffected
+         */
+        assertEquals("Original dataset sequence was modified",
+                copySequenceFeatures,
+                copySeq0.getSequenceFeatures().toString());
 
+        /*
+         * verify any new dataset sequence was added to the
+         * alignment dataset
+         */
+        assertEquals("Wrong Dataset size after " + msg,
+                newDatasetSequence ? 2 : 1,
+                alignment.getDataset().getHeight());
+
+        /*
+         * undo and verify all restored
+         */
+        AlignmentI[] views = new AlignmentI[] { alignment };
+        ec.undoCommand(views);
         sfs = seq0.getSequenceFeatures();
+        assertEquals("After undo of " + msg, func(5), sfs.size());
+        verifyUndo(from, to, sfs);
+
+        /*
+         * verify copy alignment dataset sequence still unaffected
+         * and alignment dataset has shrunk (if it was added to)
+         */
+        assertEquals("Original dataset sequence was modified",
+                copySequenceFeatures,
+                copySeq0.getSequenceFeatures().toString());
+        assertEquals("Wrong Dataset size after Undo of " + msg, 1,
+                alignment.getDataset().getHeight());
 
         /*
-         * confirm the number of features has reduced by the
-         * number of features within the cut region i.e. by
-         * func(length of cut)
+         * redo and verify
          */
-        String msg = String.format("Cut %d-%d ", from, to);
-        if (to - from == 4)
-        {
-          // all columns cut
-          assertNull(sfs);
-        }
-        else
-        {
-          assertEquals(msg + "wrong number of features left", func(5)
-                  - func(to - from + 1), sfs.size());
-        }
+        ec.doCommand(views);
+        verifyCut(seq0, from, to, msg, start);
 
         /*
-         * inspect individual features
+         * verify copy alignment dataset sequence unaffected
+         * and any new dataset sequence readded to alignment dataset
          */
-        if (sfs != null)
-        {
-          for (SequenceFeature sf : sfs)
-          {
-            checkFeatureRelocation(sf, from + 1, to + 1);
-          }
-        }
+        assertEquals("Original dataset sequence was modified",
+                copySequenceFeatures,
+                copySeq0.getSequenceFeatures().toString());
+        assertEquals("Wrong Dataset size after Redo of " + msg,
+                newDatasetSequence ? 2 : 1,
+                alignment.getDataset().getHeight());
+
         /*
          * undo ready for next cut
          */
-        testee.undoCommand(new AlignmentI[] { alignment });
-        assertEquals(func(5), seq0.getSequenceFeatures().size());
+        ec.undoCommand(views);
+
+        /*
+         * final verify that copy alignment dataset sequence is still unaffected
+         * and that alignment dataset has shrunk
+         */
+        assertEquals("Original dataset sequence was modified",
+                copySequenceFeatures,
+                copySeq0.getSequenceFeatures().toString());
+        assertEquals("Wrong Dataset size after final Undo of " + msg, 1,
+                alignment.getDataset().getHeight());
       }
     }
   }
 
   /**
+   * Verify by inspection that the sequence features left on the sequence after
+   * a cut match the expected results. The trick to this is that we can parse
+   * each feature's original start-end positions from its description.
+   * 
+   * @param seq0
+   * @param from
+   * @param to
+   * @param msg
+   * @param seqStart
+   */
+  protected void verifyCut(SequenceI seq0, int from, int to,
+          final String msg, int seqStart)
+  {
+    List<SequenceFeature> sfs;
+    sfs = seq0.getSequenceFeatures();
+
+    Collections.sort(sfs, BY_DESCRIPTION);
+
+    /*
+     * confirm the number of features has reduced by the
+     * number of features within the cut region i.e. by
+     * func(length of cut); exception is a cut at start or end of sequence, 
+     * which retains the original coordinates, dataset sequence 
+     * and all its features
+     */
+    boolean datasetRetained = from == 0 || to == 4;
+    if (datasetRetained)
+    {
+      // dataset and all features retained
+      assertEquals(msg, func(5), sfs.size());
+    }
+    else if (to - from == 4)
+    {
+      // all columns were cut
+      assertTrue(sfs.isEmpty());
+    }
+    else
+    {
+      // failure in checkFeatureRelocation is more informative!
+      assertEquals(msg + "wrong number of features left", func(5)
+              - func(to - from + 1), sfs.size());
+    }
+
+    /*
+     * inspect individual features
+     */
+    for (SequenceFeature sf : sfs)
+    {
+      verifyFeatureRelocation(sf, from + 1, to + 1, !datasetRetained,
+              seqStart);
+    }
+  }
+
+  /**
+   * Check that after Undo, every feature has start/end that match its original
+   * "start" and "end" properties
+   * 
+   * @param from
+   * @param to
+   * @param sfs
+   */
+  protected void verifyUndo(int from, int to, List<SequenceFeature> sfs)
+  {
+    for (SequenceFeature sf : sfs)
+    {
+      final int oldFrom = ((Integer) sf.getValue("from")).intValue();
+      final int oldTo = ((Integer) sf.getValue("to")).intValue();
+      String msg = String.format(
+              "Undo cut of [%d-%d], feature at [%d-%d] ", from + 1, to + 1,
+              oldFrom, oldTo);
+      assertEquals(msg + "start", oldFrom, sf.getBegin());
+      assertEquals(msg + "end", oldTo, sf.getEnd());
+    }
+  }
+
+  /**
    * Helper method to check a feature has been correctly relocated after a cut
    * 
    * @param sf
    * @param from
-   *          start of cut (first residue cut)
+   *          start of cut (first residue cut 1..)
    * @param to
-   *          end of cut (last residue cut)
+   *          end of cut (last residue cut 1..)
+   * @param newDataset
+   * @param seqStart
    */
-  private void checkFeatureRelocation(SequenceFeature sf, int from, int to)
+  private void verifyFeatureRelocation(SequenceFeature sf, int from, int to,
+ boolean newDataset, int seqStart)
   {
     // TODO handle the gapped sequence case as well
     int cutSize = to - from + 1;
-    int oldFrom = ((Integer) sf.getValue("from")).intValue();
-    int oldTo = ((Integer) sf.getValue("to")).intValue();
+    final int oldFrom = ((Integer) sf.getValue("from")).intValue();
+    final int oldTo = ((Integer) sf.getValue("to")).intValue();
+    final int oldFromPosition = oldFrom - seqStart + 1; // 1..
+    final int oldToPosition = oldTo - seqStart + 1; // 1..
 
     String msg = String.format(
             "Feature %s relocated to %d-%d after cut of %d-%d",
             sf.getDescription(), sf.getBegin(), sf.getEnd(), from, to);
-    if (oldTo < from)
+    if (!newDataset)
+    {
+      // dataset retained with all features unchanged
+      assertEquals("0: " + msg, oldFrom, sf.getBegin());
+      assertEquals("0: " + msg, oldTo, sf.getEnd());
+    }
+    else if (oldToPosition < from)
     {
       // before cut region so unchanged
       assertEquals("1: " + msg, oldFrom, sf.getBegin());
       assertEquals("2: " + msg, oldTo, sf.getEnd());
     }
-    else if (oldFrom > to)
+    else if (oldFromPosition > to)
     {
       // follows cut region - shift by size of cut
-      assertEquals("3: " + msg, oldFrom - cutSize, sf.getBegin());
-      assertEquals("4: " + msg, oldTo - cutSize, sf.getEnd());
+      assertEquals("3: " + msg, newDataset ? oldFrom - cutSize : oldFrom,
+              sf.getBegin());
+      assertEquals("4: " + msg, newDataset ? oldTo - cutSize : oldTo,
+              sf.getEnd());
     }
-    else if (oldFrom < from && oldTo > to)
+    else if (oldFromPosition < from && oldToPosition > to)
     {
       // feature encloses cut region - shrink it right
       assertEquals("5: " + msg, oldFrom, sf.getBegin());
       assertEquals("6: " + msg, oldTo - cutSize, sf.getEnd());
     }
-    else if (oldFrom < from)
+    else if (oldFromPosition < from)
     {
       // feature overlaps left side of cut region - truncated right
-      assertEquals("7: " + msg, from - 1, sf.getEnd());
+      assertEquals("7: " + msg, from - 1 + seqStart - 1, sf.getEnd());
     }
-    else if (oldTo > to)
+    else if (oldToPosition > to)
     {
       // feature overlaps right side of cut region - truncated left
-      assertEquals("8: " + msg, from, sf.getBegin());
-      assertEquals("9: " + msg, from + oldTo - to - 1, sf.getEnd());
+      assertEquals("8: " + msg, newDataset ? from + seqStart - 1 : to + 1,
+              sf.getBegin());
+      assertEquals("9: " + msg, newDataset ? from + oldTo - to - 1 : oldTo,
+              sf.getEnd());
     }
     else
     {
@@ -844,30 +1132,32 @@ public class EditCommandTest
    * Test a cut action's relocation of sequence features
    */
   @Test(groups = { "Functional" })
-  public void testCut_gappedWithFeatures()
+  public void testCut_withFeatures5prime()
   {
+    SequenceI seq0 = new Sequence("seq/8-11", "A-BCC");
+    seq0.createDatasetSequence();
+    assertEquals(8, seq0.getStart());
+    seq0.addSequenceFeature(new SequenceFeature("", "", 10, 11, 0f,
+            null));
+    SequenceI[] seqsArray = new SequenceI[] { seq0 };
+    AlignmentI alignment = new Alignment(seqsArray);
+
     /*
-     * create sequence features before, after and overlapping
-     * a cut of columns/residues 4-7
+     * cut columns of A-B; same dataset sequence is retained, aligned sequence
+     * start becomes 10
      */
-    SequenceI seq0 = new Sequence("seq", "A-BCC");
-    seq0.addSequenceFeature(new SequenceFeature("", "", 3, 4, 0f,
-            null));
-    AlignmentI alignment = new Alignment(new SequenceI[] { seq0 });
-    // cut columns of A-B
-    Edit ec = testee.new Edit(Action.CUT, seqs, 0, 3, alignment); // cols 0-3
-                                                                  // base 0
+    Edit ec = testee.new Edit(Action.CUT, seqsArray, 0, 3, alignment);
     EditCommand.cut(ec, new AlignmentI[] { alignment });
   
     /*
-     * feature on CC(3-4) should now be on CC(1-2)
+     * feature on CC(10-11) should still be on CC(10-11)
      */
+    assertSame(seq0, alignment.getSequenceAt(0));
+    assertEquals(10, seq0.getStart());
     List<SequenceFeature> sfs = seq0.getSequenceFeatures();
     assertEquals(1, sfs.size());
     SequenceFeature sf = sfs.get(0);
-    assertEquals(1, sf.getBegin());
-    assertEquals(2, sf.getEnd());
-
-    // TODO add further cases including Undo - see JAL-2541
+    assertEquals(10, sf.getBegin());
+    assertEquals(11, sf.getEnd());
   }
 }