X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=test%2Fjalview%2Fcommands%2FEditCommandTest.java;h=155f00eee8972351d8f38ecf5a66c123eb7aaa4e;hb=2f0267f5df56bb5969b3946434d45110bf994ce7;hp=6c570d5d505785948eede862a94e1774c227ecea;hpb=c93b9ad2ebfab4cad4608a8890132918589576be;p=jalview.git diff --git a/test/jalview/commands/EditCommandTest.java b/test/jalview/commands/EditCommandTest.java index 6c570d5..155f00e 100644 --- a/test/jalview/commands/EditCommandTest.java +++ b/test/jalview/commands/EditCommandTest.java @@ -1,6 +1,27 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ 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 . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.commands; import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNull; import static org.testng.AssertJUnit.assertSame; import jalview.commands.EditCommand.Action; @@ -8,10 +29,16 @@ import jalview.commands.EditCommand.Edit; import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentI; import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; +import jalview.datamodel.features.SequenceFeatures; +import jalview.gui.JvOptionPane; +import java.util.List; import java.util.Map; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -23,6 +50,21 @@ import org.testng.annotations.Test; */ public class EditCommandTest { + /* + * compute n(n+1)/2 e.g. + * func(5) = 5 + 4 + 3 + 2 + 1 = 15 + */ + private static int func(int i) + { + return i * (i + 1) / 2; + } + + @BeforeClass(alwaysRun = true) + public void setUpJvOptionPane() + { + JvOptionPane.setInteractiveMode(false); + JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION); + } private EditCommand testee; @@ -30,7 +72,7 @@ public class EditCommandTest private Alignment al; - @BeforeMethod + @BeforeMethod(alwaysRun = true) public void setUp() { testee = new EditCommand(); @@ -50,7 +92,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 @@ -67,7 +109,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); @@ -81,12 +123,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()); @@ -102,7 +143,7 @@ public class EditCommandTest /** * Test a Paste action, where this adds sequences to an alignment. */ - @Test(enabled = false) + @Test(groups = { "Functional" }, enabled = false) // TODO fix so it works public void testPaste_addToAlignment() { @@ -111,8 +152,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()); @@ -122,15 +162,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()); @@ -140,14 +179,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()); @@ -158,7 +196,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) @@ -177,8 +215,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()); } @@ -187,7 +224,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); @@ -195,8 +232,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()); @@ -205,14 +241,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()); @@ -222,13 +257,13 @@ 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); + seqs[1].createDatasetSequence(); + 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()); @@ -239,24 +274,24 @@ public class EditCommandTest * Test that the addEdit command correctly merges insert gap commands when * possible. */ - @Test + @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); + 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); + 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); + 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()); @@ -266,8 +301,8 @@ public class EditCommandTest /* * Add a non-contiguous edit - should not be merged. */ - e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] - { edited }, 5, 2, al); + 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()); @@ -276,8 +311,8 @@ public class EditCommandTest /* * Add a Delete after the Insert - should not be merged. */ - e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[] - { edited }, 6, 2, al); + 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()); @@ -289,29 +324,29 @@ public class EditCommandTest * Test that the addEdit command correctly merges delete gap commands when * possible. */ - @Test + @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); + 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); + 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); + 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()); @@ -321,8 +356,8 @@ public class EditCommandTest /* * Add a non-contiguous edit - should not be merged. */ - e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[] - { edited }, 2, 1, al); + 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()); @@ -332,8 +367,8 @@ public class EditCommandTest /* * Add an Insert after the Delete - should not be merged. */ - e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] - { edited }, 1, 1, al); + 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()); @@ -346,12 +381,12 @@ public class EditCommandTest * case when they appear contiguous but are acting on different sequences. * They should not be merged. */ - @Test + @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); + Edit e = new EditCommand().new Edit(Action.DELETE_GAP, + new SequenceI[] { seqs[0] }, 4, 1, al); testee.addEdit(e); seqs[1].setSequence("f??ghjklmnopq"); @@ -367,21 +402,21 @@ public class EditCommandTest * Test that the addEdit command correctly merges insert gap commands acting * on a multi-sequence selection. */ - @Test + @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); + 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); + e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] { + seq1edited, seq2edited }, 2, 1, al); testee.addEdit(e); assertEquals(1, testee.getSize()); @@ -403,20 +438,19 @@ public class EditCommandTest *
  • last: --A--B-CDEF
  • * */ - @Test + @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[] seqs = new SequenceI[] - { seq }; - Edit e = command.new Edit(Action.INSERT_GAP, seqs, 1, 2, '-'); + 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, seqs, 4, 1, '-'); + e = command.new Edit(Action.INSERT_GAP, sqs, 4, 1, '-'); command.addEdit(e); - e = command.new Edit(Action.INSERT_GAP, seqs, 0, 2, '-'); + e = command.new Edit(Action.INSERT_GAP, sqs, 0, 2, '-'); command.addEdit(e); Map unwound = command.priorState(false); @@ -431,18 +465,17 @@ public class EditCommandTest *
  • End: ABC
  • * */ - @Test + @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[] seqs = new SequenceI[] - { seq }; - Edit e = command.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-'); + 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, seqs, 2, 1, '-'); + e = command.new Edit(Action.DELETE_GAP, sqs, 2, 1, '-'); command.addEdit(e); Map unwound = command.priorState(false); @@ -452,18 +485,17 @@ public class EditCommandTest /** * Test for 'undoing' a single delete edit. */ - @Test + @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[] seqs = new SequenceI[] - { seq }; - Edit e = command.new Edit(Action.DELETE_GAP, seqs, 2, 2, '-'); + SequenceI[] sqs = new SequenceI[] { seq }; + Edit e = command.new Edit(Action.DELETE_GAP, sqs, 2, 2, '-'); command.addEdit(e); - + Map unwound = command.priorState(false); assertEquals("AB--CDEF", unwound.get(ds).getSequenceAsString()); } @@ -471,27 +503,52 @@ public class EditCommandTest /** * Test 'undoing' a single gap insertion edit command. */ - @Test + @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[] seqs = new SequenceI[] - { seq }; - Edit e = command.new Edit(Action.INSERT_GAP, seqs, 2, 3, '-'); + SequenceI[] sqs = new SequenceI[] { seq }; + Edit e = command.new Edit(Action.INSERT_GAP, sqs, 2, 3, '-'); command.addEdit(e); - + Map unwound = command.priorState(false); - assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString()); + 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 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 + @Test(groups = { "Functional" }) public void testPriorState_removeGapsMultipleSeqs() { EditCommand command = new EditCommand(); @@ -505,32 +562,28 @@ public class EditCommandTest 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, '-'); + 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); - seqs = new SequenceI[] - { seq }; - e = command.new Edit(Action.DELETE_GAP, seqs, 3, 1, '-'); + 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); - seqs = new SequenceI[] - { seq }; - e = command.new Edit(Action.DELETE_GAP, seqs, 2, 1, '-'); + sqs = new SequenceI[] { seq }; + e = command.new Edit(Action.DELETE_GAP, sqs, 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, '-'); + sqs = new SequenceI[] { seq }; + e = command.new Edit(Action.DELETE_GAP, sqs, 4, 2, '-'); command.addEdit(e); /* @@ -539,9 +592,8 @@ public class EditCommandTest 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, '-'); + sqs = new SequenceI[] { seq }; + e = command.new Edit(Action.DELETE_GAP, sqs, 1, 1, '-'); command.addEdit(e); Map unwound = command.priorState(false); @@ -555,7 +607,7 @@ public class EditCommandTest * series Delete Gap edits that each act on all sequences that share a gapped * column region. */ - @Test + @Test(groups = { "Functional" }) public void testPriorState_removeGappedCols() { EditCommand command = new EditCommand(); @@ -575,9 +627,8 @@ public class EditCommandTest 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, '-'); + SequenceI[] sqs = new SequenceI[] { seq1, seq2, seq3 }; + Edit e = command.new Edit(Action.DELETE_GAP, sqs, 0, 1, '-'); command.addEdit(e); /* @@ -589,9 +640,8 @@ public class EditCommandTest 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, '-'); + sqs = new SequenceI[] { seq1, seq2, seq3 }; + e = command.new Edit(Action.DELETE_GAP, sqs, 4, 2, '-'); command.addEdit(e); Map unwound = command.priorState(false); @@ -602,4 +652,222 @@ public class EditCommandTest assertEquals(ds2, unwound.get(ds2).getDatasetSequence()); assertEquals(ds3, unwound.get(ds3).getDatasetSequence()); } + + /** + * Test a cut action's relocation of sequence features + */ + @Test(groups = { "Functional" }) + public void testCut_withFeatures() + { + /* + * create sequence features before, after and overlapping + * a cut of columns/residues 4-7 + */ + SequenceI seq0 = seqs[0]; + seq0.addSequenceFeature(new SequenceFeature("before", "", 1, 3, 0f, + null)); + seq0.addSequenceFeature(new SequenceFeature("overlap left", "", 2, 6, + 0f, null)); + seq0.addSequenceFeature(new SequenceFeature("internal", "", 5, 6, 0f, + null)); + seq0.addSequenceFeature(new SequenceFeature("overlap right", "", 7, 8, + 0f, null)); + seq0.addSequenceFeature(new SequenceFeature("after", "", 8, 10, 0f, + null)); + + Edit ec = testee.new Edit(Action.CUT, seqs, 3, 4, al); // cols 3-6 base 0 + EditCommand.cut(ec, new AlignmentI[] { al }); + + List sfs = seq0.getSequenceFeatures(); + SequenceFeatures.sortFeatures(sfs, true); + + assertEquals(4, sfs.size()); // feature internal to cut has been deleted + SequenceFeature sf = sfs.get(0); + assertEquals("before", sf.getType()); + assertEquals(1, sf.getBegin()); + assertEquals(3, sf.getEnd()); + sf = sfs.get(1); + 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 + } + + /** + * Test a cut action's relocation of sequence features, with full coverage of + * all possible feature and cut locations for a 5-position ungapped sequence + */ + @Test(groups = { "Functional" }) + public void testCut_withFeatures_exhaustive() + { + /* + * create a sequence features on each subrange of 1-5 + */ + SequenceI seq0 = new Sequence("seq", "ABCDE"); + AlignmentI alignment = new Alignment(new SequenceI[] { seq0 }); + alignment.setDataset(null); + for (int from = 1; from <= seq0.getLength(); from++) + { + for (int to = from; to <= seq0.getLength(); to++) + { + String desc = String.format("%d-%d", from, to); + SequenceFeature sf = new SequenceFeature("test", desc, from, to, + 0f, + null); + sf.setValue("from", Integer.valueOf(from)); + sf.setValue("to", Integer.valueOf(to)); + seq0.addSequenceFeature(sf); + } + } + // sanity check + List sfs = seq0.getSequenceFeatures(); + assertEquals(func(5), sfs.size()); + + /* + * now perform all possible cuts of subranges of 1-5 (followed by Undo) + * 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 to = 2; to < 3; to++) + { + testee.appendEdit(Action.CUT, sqs, from, (to - from + 1), + alignment, true); + + sfs = seq0.getSequenceFeatures(); + + /* + * confirm the number of features has reduced by the + * number of features within the cut region i.e. by + * func(length of cut) + */ + 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()); + } + + /* + * inspect individual features + */ + if (sfs != null) + { + for (SequenceFeature sf : sfs) + { + checkFeatureRelocation(sf, from + 1, to + 1); + } + } + /* + * undo ready for next cut + */ + testee.undoCommand(new AlignmentI[] { alignment }); + assertEquals(func(5), seq0.getSequenceFeatures().size()); + } + } + } + + /** + * Helper method to check a feature has been correctly relocated after a cut + * + * @param sf + * @param from + * start of cut (first residue cut) + * @param to + * end of cut (last residue cut) + */ + private void checkFeatureRelocation(SequenceFeature sf, int from, int to) + { + // 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(); + + 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) + { + // before cut region so unchanged + assertEquals("1: " + msg, oldFrom, sf.getBegin()); + assertEquals("2: " + msg, oldTo, sf.getEnd()); + } + else if (oldFrom > to) + { + // follows cut region - shift by size of cut + assertEquals("3: " + msg, oldFrom - cutSize, sf.getBegin()); + assertEquals("4: " + msg, oldTo - cutSize, sf.getEnd()); + } + else if (oldFrom < from && oldTo > 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) + { + // feature overlaps left side of cut region - truncated right + assertEquals("7: " + msg, from - 1, sf.getEnd()); + } + else if (oldTo > 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()); + } + else + { + // feature internal to cut - should have been deleted! + Assert.fail(msg + " - should have been deleted"); + } + } + + /** + * Test a cut action's relocation of sequence features + */ + @Test(groups = { "Functional" }) + public void testCut_gappedWithFeatures() + { + /* + * create sequence features before, after and overlapping + * a cut of columns/residues 4-7 + */ + 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 + EditCommand.cut(ec, new AlignmentI[] { alignment }); + + /* + * feature on CC(3-4) should now be on CC(1-2) + */ + List 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 + } }