package jalview.commands; 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; import jalview.datamodel.AlignmentI; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceI; import java.util.Map; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * Unit tests for EditCommand * * @author gmcarstairs * */ public class EditCommandTest { private EditCommand testee; private SequenceI[] seqs; private Alignment al; @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('?'); } /** * Test inserting gap characters */ @Test(groups ={ "Functional" }) public void testAppendEdit_insertGap() { // set a non-standard gap character to prove it is actually used testee.appendEdit(Action.INSERT_GAP, seqs, 4, 3, al, true); assertEquals("abcd???efghjk", seqs[0].getSequenceAsString()); assertEquals("fghj???klmnopq", seqs[1].getSequenceAsString()); assertEquals("qrst???uvwxyz", seqs[2].getSequenceAsString()); assertEquals("1234???567890", seqs[3].getSequenceAsString()); // todo: test for handling out of range positions? } /** * Test deleting characters from sequences. Note the deleteGap() action does * not check that only gap characters are being removed. */ @Test(groups ={ "Functional" }) public void testAppendEdit_deleteGap() { testee.appendEdit(Action.DELETE_GAP, seqs, 4, 3, al, true); assertEquals("abcdhjk", seqs[0].getSequenceAsString()); assertEquals("fghjnopq", seqs[1].getSequenceAsString()); assertEquals("qrstxyz", seqs[2].getSequenceAsString()); assertEquals("1234890", seqs[3].getSequenceAsString()); } /** * Test a cut action. The command should store the cut characters to support * undo. */ @Test(groups ={ "Functional" }) public void testCut() { Edit ec = testee.new Edit(Action.CUT, seqs, 4, 3, al); testee.cut(ec, new AlignmentI[] { al }); assertEquals("abcdhjk", seqs[0].getSequenceAsString()); assertEquals("fghjnopq", seqs[1].getSequenceAsString()); assertEquals("qrstxyz", seqs[2].getSequenceAsString()); assertEquals("1234890", seqs[3].getSequenceAsString()); assertEquals("efg", new String(ec.string[0])); assertEquals("klm", new String(ec.string[1])); assertEquals("uvw", new String(ec.string[2])); assertEquals("567", new String(ec.string[3])); // TODO: case where whole sequence is deleted as nothing left; etc } /** * Test a Paste action, where this adds sequences to an alignment. */ @Test(groups = { "Functional" }, enabled = false) // TODO fix so it works public void testPaste_addToAlignment() { 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); testee.paste(ec, new AlignmentI[] { al }); assertEquals(6, al.getSequences().size()); assertEquals("1234567890", seqs[3].getSequenceAsString()); assertEquals("ACEFKL", seqs[4].getSequenceAsString()); assertEquals("JWMPDH", seqs[5].getSequenceAsString()); } /** * Test insertGap followed by undo command */ @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 }); assertEquals("abcdefghjk", seqs[0].getSequenceAsString()); assertEquals("fghjklmnopq", seqs[1].getSequenceAsString()); assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString()); assertEquals("1234567890", seqs[3].getSequenceAsString()); } /** * Test deleteGap followed by undo command */ @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 }); // deleteGap doesn't 'remember' deleted characters, only gaps get put back assertEquals("abcd???hjk", seqs[0].getSequenceAsString()); assertEquals("fghj???nopq", seqs[1].getSequenceAsString()); assertEquals("qrst???xyz", seqs[2].getSequenceAsString()); assertEquals("1234???890", seqs[3].getSequenceAsString()); } /** * Test several commands followed by an undo command */ @Test(groups ={ "Functional" }) public void testUndo_multipleCommands() { // delete positions 3/4/5 (counting from 1) testee.appendEdit(Action.DELETE_GAP, seqs, 2, 3, al, true); assertEquals("abfghjk", seqs[0].getSequenceAsString()); assertEquals("1267890", seqs[3].getSequenceAsString()); // insert 2 gaps after the second residue testee.appendEdit(Action.INSERT_GAP, seqs, 2, 2, al, true); assertEquals("ab??fghjk", seqs[0].getSequenceAsString()); assertEquals("12??67890", seqs[3].getSequenceAsString()); // delete positions 4/5/6 testee.appendEdit(Action.DELETE_GAP, seqs, 3, 3, al, true); assertEquals("ab?hjk", seqs[0].getSequenceAsString()); assertEquals("12?890", seqs[3].getSequenceAsString()); // undo edit commands testee.undoCommand(new AlignmentI[] { al }); assertEquals("ab?????hjk", seqs[0].getSequenceAsString()); assertEquals("12?????890", seqs[3].getSequenceAsString()); } /** * Unit test for JAL-1594 bug: click and drag sequence right to insert gaps - * undo did not remove them all. */ @Test(groups ={ "Functional" }) public void testUndo_multipleInsertGaps() { testee.appendEdit(Action.INSERT_GAP, seqs, 4, 1, al, true); testee.appendEdit(Action.INSERT_GAP, seqs, 5, 1, al, true); testee.appendEdit(Action.INSERT_GAP, seqs, 6, 1, al, true); // undo edit commands testee.undoCommand(new AlignmentI[] { al }); assertEquals("abcdefghjk", seqs[0].getSequenceAsString()); assertEquals("1234567890", seqs[3].getSequenceAsString()); } /** * Test cut followed by undo command */ @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 }); assertEquals("abcdefghjk", seqs[0].getSequenceAsString()); assertEquals("fghjklmnopq", seqs[1].getSequenceAsString()); assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString()); assertEquals("1234567890", seqs[3].getSequenceAsString()); } /** * Test the replace command (used to manually edit a sequence) */ @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); 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. *