JAL-1925 update source version in license
[jalview.git] / test / jalview / commands / EditCommandTest.java
index fc821b9..00a759e 100644 (file)
@@ -1,6 +1,28 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.9.0b2)
+ * Copyright (C) 2015 The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
 package jalview.commands;
 
-import static org.junit.Assert.assertEquals;
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertSame;
+
 import jalview.commands.EditCommand.Action;
 import jalview.commands.EditCommand.Edit;
 import jalview.datamodel.Alignment;
@@ -8,9 +30,10 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
+import java.util.Map;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
 
 /**
  * Unit tests for EditCommand
@@ -27,15 +50,19 @@ public class EditCommandTest
 
   private Alignment al;
 
-  @Before
+  @BeforeMethod(alwaysRun = true)
   public void setUp()
   {
     testee = new EditCommand();
     seqs = new SequenceI[4];
     seqs[0] = new Sequence("seq0", "abcdefghjk");
+    seqs[0].setDatasetSequence(new Sequence("seq0ds", "abcdefghjk"));
     seqs[1] = new Sequence("seq1", "fghjklmnopq");
+    seqs[1].setDatasetSequence(new Sequence("seq1ds", "fghjklmnopq"));
     seqs[2] = new Sequence("seq2", "qrstuvwxyz");
+    seqs[2].setDatasetSequence(new Sequence("seq2ds", "qrstuvwxyz"));
     seqs[3] = new Sequence("seq3", "1234567890");
+    seqs[3].setDatasetSequence(new Sequence("seq3ds", "1234567890"));
     al = new Alignment(seqs);
     al.setGapCharacter('?');
   }
@@ -43,7 +70,7 @@ public class EditCommandTest
   /**
    * Test inserting gap characters
    */
-  @Test
+  @Test(groups = { "Functional" })
   public void testAppendEdit_insertGap()
   {
     // set a non-standard gap character to prove it is actually used
@@ -60,7 +87,7 @@ public class EditCommandTest
    * Test deleting characters from sequences. Note the deleteGap() action does
    * not check that only gap characters are being removed.
    */
-  @Test
+  @Test(groups = { "Functional" })
   public void testAppendEdit_deleteGap()
   {
     testee.appendEdit(Action.DELETE_GAP, seqs, 4, 3, al, true);
@@ -74,12 +101,11 @@ public class EditCommandTest
    * Test a cut action. The command should store the cut characters to support
    * undo.
    */
-  @Test
+  @Test(groups = { "Functional" })
   public void testCut()
   {
     Edit ec = testee.new Edit(Action.CUT, seqs, 4, 3, al);
-    testee.cut(ec, new AlignmentI[]
-    { al });
+    EditCommand.cut(ec, new AlignmentI[] { al });
     assertEquals("abcdhjk", seqs[0].getSequenceAsString());
     assertEquals("fghjnopq", seqs[1].getSequenceAsString());
     assertEquals("qrstxyz", seqs[2].getSequenceAsString());
@@ -95,8 +121,7 @@ public class EditCommandTest
   /**
    * Test a Paste action, where this adds sequences to an alignment.
    */
-  @Test
-  @Ignore
+  @Test(groups = { "Functional" }, enabled = false)
   // TODO fix so it works
   public void testPaste_addToAlignment()
   {
@@ -105,8 +130,7 @@ public class EditCommandTest
     newSeqs[1] = new Sequence("newseq1", "JWMPDH");
 
     Edit ec = testee.new Edit(Action.PASTE, newSeqs, 0, al.getWidth(), al);
-    testee.paste(ec, new AlignmentI[]
-    { al });
+    EditCommand.paste(ec, new AlignmentI[] { al });
     assertEquals(6, al.getSequences().size());
     assertEquals("1234567890", seqs[3].getSequenceAsString());
     assertEquals("ACEFKL", seqs[4].getSequenceAsString());
@@ -116,15 +140,14 @@ public class EditCommandTest
   /**
    * Test insertGap followed by undo command
    */
-  @Test
+  @Test(groups = { "Functional" })
   public void testUndo_insertGap()
   {
     // Edit ec = testee.new Edit(Action.INSERT_GAP, seqs, 4, 3, '?');
     testee.appendEdit(Action.INSERT_GAP, seqs, 4, 3, al, true);
     // check something changed
     assertEquals("abcd???efghjk", seqs[0].getSequenceAsString());
-    testee.undoCommand(new AlignmentI[]
-    { al });
+    testee.undoCommand(new AlignmentI[] { al });
     assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
     assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
     assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
@@ -134,14 +157,13 @@ public class EditCommandTest
   /**
    * Test deleteGap followed by undo command
    */
-  @Test
+  @Test(groups = { "Functional" })
   public void testUndo_deleteGap()
   {
     testee.appendEdit(Action.DELETE_GAP, seqs, 4, 3, al, true);
     // check something changed
     assertEquals("abcdhjk", seqs[0].getSequenceAsString());
-    testee.undoCommand(new AlignmentI[]
-    { al });
+    testee.undoCommand(new AlignmentI[] { al });
     // deleteGap doesn't 'remember' deleted characters, only gaps get put back
     assertEquals("abcd???hjk", seqs[0].getSequenceAsString());
     assertEquals("fghj???nopq", seqs[1].getSequenceAsString());
@@ -152,7 +174,7 @@ public class EditCommandTest
   /**
    * Test several commands followed by an undo command
    */
-  @Test
+  @Test(groups = { "Functional" })
   public void testUndo_multipleCommands()
   {
     // delete positions 3/4/5 (counting from 1)
@@ -171,8 +193,7 @@ public class EditCommandTest
     assertEquals("12?890", seqs[3].getSequenceAsString());
 
     // undo edit commands
-    testee.undoCommand(new AlignmentI[]
-    { al });
+    testee.undoCommand(new AlignmentI[] { al });
     assertEquals("ab?????hjk", seqs[0].getSequenceAsString());
     assertEquals("12?????890", seqs[3].getSequenceAsString());
   }
@@ -181,7 +202,7 @@ public class EditCommandTest
    * Unit test for JAL-1594 bug: click and drag sequence right to insert gaps -
    * undo did not remove them all.
    */
-  @Test
+  @Test(groups = { "Functional" })
   public void testUndo_multipleInsertGaps()
   {
     testee.appendEdit(Action.INSERT_GAP, seqs, 4, 1, al, true);
@@ -189,8 +210,7 @@ public class EditCommandTest
     testee.appendEdit(Action.INSERT_GAP, seqs, 6, 1, al, true);
 
     // undo edit commands
-    testee.undoCommand(new AlignmentI[]
-    { al });
+    testee.undoCommand(new AlignmentI[] { al });
     assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
     assertEquals("1234567890", seqs[3].getSequenceAsString());
 
@@ -199,14 +219,13 @@ public class EditCommandTest
   /**
    * Test cut followed by undo command
    */
-  @Test
+  @Test(groups = { "Functional" })
   public void testUndo_cut()
   {
     testee.appendEdit(Action.CUT, seqs, 4, 3, al, true);
     // check something changed
     assertEquals("abcdhjk", seqs[0].getSequenceAsString());
-    testee.undoCommand(new AlignmentI[]
-    { al });
+    testee.undoCommand(new AlignmentI[] { al });
     assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
     assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
     assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
@@ -216,17 +235,399 @@ public class EditCommandTest
   /**
    * Test the replace command (used to manually edit a sequence)
    */
-  @Test
+  @Test(groups = { "Functional" })
   public void testReplace()
   {
     // seem to need a dataset sequence on the edited sequence here
     seqs[1].setDatasetSequence(seqs[1]);
-    new EditCommand("", Action.REPLACE, "ZXY", new SequenceI[]
-    { seqs[1] }, 4, 8, al);
+    new EditCommand("", Action.REPLACE, "ZXY", new SequenceI[] { seqs[1] },
+            4, 8, al);
     assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
     assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
     assertEquals("1234567890", seqs[3].getSequenceAsString());
     seqs[1] = new Sequence("seq1", "fghjZXYnopq");
+  }
+
+  /**
+   * Test that the addEdit command correctly merges insert gap commands when
+   * possible.
+   */
+  @Test(groups = { "Functional" })
+  public void testAddEdit_multipleInsertGap()
+  {
+    /*
+     * 3 insert gap in a row (aka mouse drag right):
+     */
+    Edit e = new EditCommand().new Edit(Action.INSERT_GAP,
+            new SequenceI[] { seqs[0] }, 1, 1, al);
+    testee.addEdit(e);
+    SequenceI edited = new Sequence("seq0", "a?bcdefghjk");
+    edited.setDatasetSequence(seqs[0].getDatasetSequence());
+    e = new EditCommand().new Edit(Action.INSERT_GAP,
+            new SequenceI[] { edited }, 2, 1, al);
+    testee.addEdit(e);
+    edited = new Sequence("seq0", "a??bcdefghjk");
+    edited.setDatasetSequence(seqs[0].getDatasetSequence());
+    e = new EditCommand().new Edit(Action.INSERT_GAP,
+            new SequenceI[] { edited }, 3, 1, al);
+    testee.addEdit(e);
+    assertEquals(1, testee.getSize());
+    assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
+    assertEquals(1, testee.getEdit(0).getPosition());
+    assertEquals(3, testee.getEdit(0).getNumber());
+
+    /*
+     * Add a non-contiguous edit - should not be merged.
+     */
+    e = new EditCommand().new Edit(Action.INSERT_GAP,
+            new SequenceI[] { edited }, 5, 2, al);
+    testee.addEdit(e);
+    assertEquals(2, testee.getSize());
+    assertEquals(5, testee.getEdit(1).getPosition());
+    assertEquals(2, testee.getEdit(1).getNumber());
+
+    /*
+     * Add a Delete after the Insert - should not be merged.
+     */
+    e = new EditCommand().new Edit(Action.DELETE_GAP,
+            new SequenceI[] { edited }, 6, 2, al);
+    testee.addEdit(e);
+    assertEquals(3, testee.getSize());
+    assertEquals(Action.DELETE_GAP, testee.getEdit(2).getAction());
+    assertEquals(6, testee.getEdit(2).getPosition());
+    assertEquals(2, testee.getEdit(2).getNumber());
+  }
+
+  /**
+   * Test that the addEdit command correctly merges delete gap commands when
+   * possible.
+   */
+  @Test(groups = { "Functional" })
+  public void testAddEdit_multipleDeleteGap()
+  {
+    /*
+     * 3 delete gap in a row (aka mouse drag left):
+     */
+    seqs[0].setSequence("a???bcdefghjk");
+    Edit e = new EditCommand().new Edit(Action.DELETE_GAP,
+            new SequenceI[] { seqs[0] }, 4, 1, al);
+    testee.addEdit(e);
+    assertEquals(1, testee.getSize());
+
+    SequenceI edited = new Sequence("seq0", "a??bcdefghjk");
+    edited.setDatasetSequence(seqs[0].getDatasetSequence());
+    e = new EditCommand().new Edit(Action.DELETE_GAP,
+            new SequenceI[] { edited }, 3, 1, al);
+    testee.addEdit(e);
+    assertEquals(1, testee.getSize());
+
+    edited = new Sequence("seq0", "a?bcdefghjk");
+    edited.setDatasetSequence(seqs[0].getDatasetSequence());
+    e = new EditCommand().new Edit(Action.DELETE_GAP,
+            new SequenceI[] { edited }, 2, 1, al);
+    testee.addEdit(e);
+    assertEquals(1, testee.getSize());
+    assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
+    assertEquals(2, testee.getEdit(0).getPosition());
+    assertEquals(3, testee.getEdit(0).getNumber());
+
+    /*
+     * Add a non-contiguous edit - should not be merged.
+     */
+    e = new EditCommand().new Edit(Action.DELETE_GAP,
+            new SequenceI[] { edited }, 2, 1, al);
+    testee.addEdit(e);
+    assertEquals(2, testee.getSize());
+    assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
+    assertEquals(2, testee.getEdit(1).getPosition());
+    assertEquals(1, testee.getEdit(1).getNumber());
+
+    /*
+     * Add an Insert after the Delete - should not be merged.
+     */
+    e = new EditCommand().new Edit(Action.INSERT_GAP,
+            new SequenceI[] { edited }, 1, 1, al);
+    testee.addEdit(e);
+    assertEquals(3, testee.getSize());
+    assertEquals(Action.INSERT_GAP, testee.getEdit(2).getAction());
+    assertEquals(1, testee.getEdit(2).getPosition());
+    assertEquals(1, testee.getEdit(2).getNumber());
+  }
+
+  /**
+   * Test that the addEdit command correctly handles 'remove gaps' edits for the
+   * case when they appear contiguous but are acting on different sequences.
+   * They should not be merged.
+   */
+  @Test(groups = { "Functional" })
+  public void testAddEdit_removeAllGaps()
+  {
+    seqs[0].setSequence("a???bcdefghjk");
+    Edit e = new EditCommand().new Edit(Action.DELETE_GAP,
+            new SequenceI[] { seqs[0] }, 4, 1, al);
+    testee.addEdit(e);
+
+    seqs[1].setSequence("f??ghjklmnopq");
+    Edit e2 = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
+    { seqs[1] }, 3, 1, al);
+    testee.addEdit(e2);
+    assertEquals(2, testee.getSize());
+    assertSame(e, testee.getEdit(0));
+    assertSame(e2, testee.getEdit(1));
+  }
+
+  /**
+   * Test that the addEdit command correctly merges insert gap commands acting
+   * on a multi-sequence selection.
+   */
+  @Test(groups = { "Functional" })
+  public void testAddEdit_groupInsertGaps()
+  {
+    /*
+     * 2 insert gap in a row (aka mouse drag right), on two sequences:
+     */
+    Edit e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] {
+        seqs[0], seqs[1] }, 1, 1, al);
+    testee.addEdit(e);
+    SequenceI seq1edited = new Sequence("seq0", "a?bcdefghjk");
+    seq1edited.setDatasetSequence(seqs[0].getDatasetSequence());
+    SequenceI seq2edited = new Sequence("seq1", "f?ghjklmnopq");
+    seq2edited.setDatasetSequence(seqs[1].getDatasetSequence());
+    e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] {
+        seq1edited, seq2edited }, 2, 1, al);
+    testee.addEdit(e);
+
+    assertEquals(1, testee.getSize());
+    assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
+    assertEquals(1, testee.getEdit(0).getPosition());
+    assertEquals(2, testee.getEdit(0).getNumber());
+    assertEquals(seqs[0].getDatasetSequence(), testee.getEdit(0)
+            .getSequences()[0].getDatasetSequence());
+    assertEquals(seqs[1].getDatasetSequence(), testee.getEdit(0)
+            .getSequences()[1].getDatasetSequence());
+  }
+
+  /**
+   * Test for 'undoing' a series of gap insertions.
+   * <ul>
+   * <li>Start: ABCDEF insert 2 at pos 1</li>
+   * <li>next: A--BCDEF insert 1 at pos 4</li>
+   * <li>next: A--B-CDEF insert 2 at pos 0</li>
+   * <li>last: --A--B-CDEF</li>
+   * </ul>
+   */
+  @Test(groups = { "Functional" })
+  public void testPriorState_multipleInserts()
+  {
+    EditCommand command = new EditCommand();
+    SequenceI seq = new Sequence("", "--A--B-CDEF");
+    SequenceI ds = new Sequence("", "ABCDEF");
+    seq.setDatasetSequence(ds);
+    SequenceI[] sqs = new SequenceI[] { seq };
+    Edit e = command.new Edit(Action.INSERT_GAP, sqs, 1, 2, '-');
+    command.addEdit(e);
+    e = command.new Edit(Action.INSERT_GAP, sqs, 4, 1, '-');
+    command.addEdit(e);
+    e = command.new Edit(Action.INSERT_GAP, sqs, 0, 2, '-');
+    command.addEdit(e);
+
+    Map<SequenceI, SequenceI> unwound = command.priorState(false);
+    assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString());
+  }
+
+  /**
+   * Test for 'undoing' a series of gap deletions.
+   * <ul>
+   * <li>Start: A-B-C delete 1 at pos 1</li>
+   * <li>Next: AB-C delete 1 at pos 2</li>
+   * <li>End: ABC</li>
+   * </ul>
+   */
+  @Test(groups = { "Functional" })
+  public void testPriorState_removeAllGaps()
+  {
+    EditCommand command = new EditCommand();
+    SequenceI seq = new Sequence("", "ABC");
+    SequenceI ds = new Sequence("", "ABC");
+    seq.setDatasetSequence(ds);
+    SequenceI[] sqs = new SequenceI[] { seq };
+    Edit e = command.new Edit(Action.DELETE_GAP, sqs, 1, 1, '-');
+    command.addEdit(e);
+    e = command.new Edit(Action.DELETE_GAP, sqs, 2, 1, '-');
+    command.addEdit(e);
+
+    Map<SequenceI, SequenceI> unwound = command.priorState(false);
+    assertEquals("A-B-C", unwound.get(ds).getSequenceAsString());
+  }
+
+  /**
+   * Test for 'undoing' a single delete edit.
+   */
+  @Test(groups = { "Functional" })
+  public void testPriorState_singleDelete()
+  {
+    EditCommand command = new EditCommand();
+    SequenceI seq = new Sequence("", "ABCDEF");
+    SequenceI ds = new Sequence("", "ABCDEF");
+    seq.setDatasetSequence(ds);
+    SequenceI[] sqs = new SequenceI[] { seq };
+    Edit e = command.new Edit(Action.DELETE_GAP, sqs, 2, 2, '-');
+    command.addEdit(e);
+
+    Map<SequenceI, SequenceI> unwound = command.priorState(false);
+    assertEquals("AB--CDEF", unwound.get(ds).getSequenceAsString());
+  }
+
+  /**
+   * Test 'undoing' a single gap insertion edit command.
+   */
+  @Test(groups = { "Functional" })
+  public void testPriorState_singleInsert()
+  {
+    EditCommand command = new EditCommand();
+    SequenceI seq = new Sequence("", "AB---CDEF");
+    SequenceI ds = new Sequence("", "ABCDEF");
+    seq.setDatasetSequence(ds);
+    SequenceI[] sqs = new SequenceI[] { seq };
+    Edit e = command.new Edit(Action.INSERT_GAP, sqs, 2, 3, '-');
+    command.addEdit(e);
+
+    Map<SequenceI, SequenceI> unwound = command.priorState(false);
+    SequenceI prior = unwound.get(ds);
+    assertEquals("ABCDEF", prior.getSequenceAsString());
+    assertEquals(1, prior.getStart());
+    assertEquals(6, prior.getEnd());
+  }
+
+  /**
+   * Test 'undoing' a single gap insertion edit command, on a sequence whose
+   * start residue is other than 1
+   */
+  @Test(groups = { "Functional" })
+  public void testPriorState_singleInsertWithOffset()
+  {
+    EditCommand command = new EditCommand();
+    SequenceI seq = new Sequence("", "AB---CDEF", 8, 13);
+    // SequenceI ds = new Sequence("", "ABCDEF", 8, 13);
+    // seq.setDatasetSequence(ds);
+    seq.createDatasetSequence();
+    SequenceI[] sqs = new SequenceI[] { seq };
+    Edit e = command.new Edit(Action.INSERT_GAP, sqs, 2, 3, '-');
+    command.addEdit(e);
+
+    Map<SequenceI, SequenceI> unwound = command.priorState(false);
+    SequenceI prior = unwound.get(seq.getDatasetSequence());
+    assertEquals("ABCDEF", prior.getSequenceAsString());
+    assertEquals(8, prior.getStart());
+    assertEquals(13, prior.getEnd());
+  }
+
+  /**
+   * Test that mimics 'remove all gaps' action. This generates delete gap edits
+   * for contiguous gaps in each sequence separately.
+   */
+  @Test(groups = { "Functional" })
+  public void testPriorState_removeGapsMultipleSeqs()
+  {
+    EditCommand command = new EditCommand();
+    String original1 = "--ABC-DEF";
+    String original2 = "FG-HI--J";
+    String original3 = "M-NOPQ";
+
+    /*
+     * Two edits for the first sequence
+     */
+    SequenceI seq = new Sequence("", "ABC-DEF");
+    SequenceI ds1 = new Sequence("", "ABCDEF");
+    seq.setDatasetSequence(ds1);
+    SequenceI[] sqs = new SequenceI[] { seq };
+    Edit e = command.new Edit(Action.DELETE_GAP, sqs, 0, 2, '-');
+    command.addEdit(e);
+    seq = new Sequence("", "ABCDEF");
+    seq.setDatasetSequence(ds1);
+    sqs = new SequenceI[] { seq };
+    e = command.new Edit(Action.DELETE_GAP, sqs, 3, 1, '-');
+    command.addEdit(e);
+
+    /*
+     * Two edits for the second sequence
+     */
+    seq = new Sequence("", "FGHI--J");
+    SequenceI ds2 = new Sequence("", "FGHIJ");
+    seq.setDatasetSequence(ds2);
+    sqs = new SequenceI[] { seq };
+    e = command.new Edit(Action.DELETE_GAP, sqs, 2, 1, '-');
+    command.addEdit(e);
+    seq = new Sequence("", "FGHIJ");
+    seq.setDatasetSequence(ds2);
+    sqs = new SequenceI[] { seq };
+    e = command.new Edit(Action.DELETE_GAP, sqs, 4, 2, '-');
+    command.addEdit(e);
+
+    /*
+     * One edit for the third sequence.
+     */
+    seq = new Sequence("", "MNOPQ");
+    SequenceI ds3 = new Sequence("", "MNOPQ");
+    seq.setDatasetSequence(ds3);
+    sqs = new SequenceI[] { seq };
+    e = command.new Edit(Action.DELETE_GAP, sqs, 1, 1, '-');
+    command.addEdit(e);
+
+    Map<SequenceI, SequenceI> unwound = command.priorState(false);
+    assertEquals(original1, unwound.get(ds1).getSequenceAsString());
+    assertEquals(original2, unwound.get(ds2).getSequenceAsString());
+    assertEquals(original3, unwound.get(ds3).getSequenceAsString());
+  }
+
+  /**
+   * Test that mimics 'remove all gapped columns' action. This generates a
+   * series Delete Gap edits that each act on all sequences that share a gapped
+   * column region.
+   */
+  @Test(groups = { "Functional" })
+  public void testPriorState_removeGappedCols()
+  {
+    EditCommand command = new EditCommand();
+    String original1 = "--ABC--DEF";
+    String original2 = "-G-HI--J";
+    String original3 = "-M-NO--PQ";
+
+    /*
+     * First edit deletes the first column.
+     */
+    SequenceI seq1 = new Sequence("", "-ABC--DEF");
+    SequenceI ds1 = new Sequence("", "ABCDEF");
+    seq1.setDatasetSequence(ds1);
+    SequenceI seq2 = new Sequence("", "G-HI--J");
+    SequenceI ds2 = new Sequence("", "GHIJ");
+    seq2.setDatasetSequence(ds2);
+    SequenceI seq3 = new Sequence("", "M-NO--PQ");
+    SequenceI ds3 = new Sequence("", "MNOPQ");
+    seq3.setDatasetSequence(ds3);
+    SequenceI[] sqs = new SequenceI[] { seq1, seq2, seq3 };
+    Edit e = command.new Edit(Action.DELETE_GAP, sqs, 0, 1, '-');
+    command.addEdit(e);
+
+    /*
+     * Second edit deletes what is now columns 4 and 5.
+     */
+    seq1 = new Sequence("", "-ABCDEF");
+    seq1.setDatasetSequence(ds1);
+    seq2 = new Sequence("", "G-HIJ");
+    seq2.setDatasetSequence(ds2);
+    seq3 = new Sequence("", "M-NOPQ");
+    seq3.setDatasetSequence(ds3);
+    sqs = new SequenceI[] { seq1, seq2, seq3 };
+    e = command.new Edit(Action.DELETE_GAP, sqs, 4, 2, '-');
+    command.addEdit(e);
 
+    Map<SequenceI, SequenceI> unwound = command.priorState(false);
+    assertEquals(original1, unwound.get(ds1).getSequenceAsString());
+    assertEquals(original2, unwound.get(ds2).getSequenceAsString());
+    assertEquals(original3, unwound.get(ds3).getSequenceAsString());
+    assertEquals(ds1, unwound.get(ds1).getDatasetSequence());
+    assertEquals(ds2, unwound.get(ds2).getDatasetSequence());
+    assertEquals(ds3, unwound.get(ds3).getDatasetSequence());
   }
 }