Merge develop to Release_2_8_3_Branch
[jalview.git] / test / jalview / commands / EditCommandTest.java
index fc821b9..6ea05e6 100644 (file)
@@ -1,6 +1,7 @@
 package jalview.commands;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
 import jalview.commands.EditCommand.Action;
 import jalview.commands.EditCommand.Edit;
 import jalview.datamodel.Alignment;
@@ -8,6 +9,8 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 
+import java.util.Map;
+
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -33,9 +36,13 @@ public class EditCommandTest
     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('?');
   }
@@ -227,6 +234,373 @@ public class EditCommandTest
     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
+  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
+  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
+  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
+  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
+  public void testPriorState_multipleInserts()
+  {
+    EditCommand command = new EditCommand();
+    SequenceI seq = new Sequence("", "--A--B-CDEF");
+    SequenceI ds = new Sequence("", "ABCDEF");
+    seq.setDatasetSequence(ds);
+    SequenceI[] seqs = new SequenceI[]
+    { seq };
+    Edit e = command.new Edit(Action.INSERT_GAP, seqs, 1, 2, '-');
+    command.addEdit(e);
+    e = command.new Edit(Action.INSERT_GAP, seqs, 4, 1, '-');
+    command.addEdit(e);
+    e = command.new Edit(Action.INSERT_GAP, seqs, 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
+  public void testPriorState_removeAllGaps()
+  {
+    EditCommand command = new EditCommand();
+    SequenceI seq = new Sequence("", "ABC");
+    SequenceI ds = new Sequence("", "ABC");
+    seq.setDatasetSequence(ds);
+    SequenceI[] seqs = new SequenceI[]
+    { seq };
+    Edit e = command.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
+    command.addEdit(e);
+    e = command.new Edit(Action.DELETE_GAP, seqs, 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
+  public void testPriorState_singleDelete()
+  {
+    EditCommand command = new EditCommand();
+    SequenceI seq = new Sequence("", "ABCDEF");
+    SequenceI ds = new Sequence("", "ABCDEF");
+    seq.setDatasetSequence(ds);
+    SequenceI[] seqs = new SequenceI[]
+    { seq };
+    Edit e = command.new Edit(Action.DELETE_GAP, seqs, 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
+  public void testPriorState_singleInsert()
+  {
+    EditCommand command = new EditCommand();
+    SequenceI seq = new Sequence("", "AB---CDEF");
+    SequenceI ds = new Sequence("", "ABCDEF");
+    seq.setDatasetSequence(ds);
+    SequenceI[] seqs = new SequenceI[]
+    { seq };
+    Edit e = command.new Edit(Action.INSERT_GAP, seqs, 2, 3, '-');
+    command.addEdit(e);
+  
+    Map<SequenceI, SequenceI> unwound = command.priorState(false);
+    assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString());
+  }
+
+  /**
+   * Test that mimics 'remove all gaps' action. This generates delete gap edits
+   * for contiguous gaps in each sequence separately.
+   */
+  @Test
+  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[] seqs = new SequenceI[]
+    { seq };
+    Edit e = command.new Edit(Action.DELETE_GAP, seqs, 0, 2, '-');
+    command.addEdit(e);
+    seq = new Sequence("", "ABCDEF");
+    seq.setDatasetSequence(ds1);
+    seqs = new SequenceI[]
+    { seq };
+    e = command.new Edit(Action.DELETE_GAP, seqs, 3, 1, '-');
+    command.addEdit(e);
+  
+    /*
+     * Two edits for the second sequence
+     */
+    seq = new Sequence("", "FGHI--J");
+    SequenceI ds2 = new Sequence("", "FGHIJ");
+    seq.setDatasetSequence(ds2);
+    seqs = new SequenceI[]
+    { seq };
+    e = command.new Edit(Action.DELETE_GAP, seqs, 2, 1, '-');
+    command.addEdit(e);
+    seq = new Sequence("", "FGHIJ");
+    seq.setDatasetSequence(ds2);
+    seqs = new SequenceI[]
+    { seq };
+    e = command.new Edit(Action.DELETE_GAP, seqs, 4, 2, '-');
+    command.addEdit(e);
+
+    /*
+     * One edit for the third sequence.
+     */
+    seq = new Sequence("", "MNOPQ");
+    SequenceI ds3 = new Sequence("", "MNOPQ");
+    seq.setDatasetSequence(ds3);
+    seqs = new SequenceI[]
+    { seq };
+    e = command.new Edit(Action.DELETE_GAP, seqs, 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
+  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[] seqs = new SequenceI[]
+    { seq1, seq2, seq3 };
+    Edit e = command.new Edit(Action.DELETE_GAP, seqs, 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);
+    seqs = new SequenceI[]
+    { seq1, seq2, seq3 };
+    e = command.new Edit(Action.DELETE_GAP, seqs, 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());
   }
 }