2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.commands;
23 import static org.testng.AssertJUnit.assertEquals;
24 import static org.testng.AssertJUnit.assertSame;
25 import static org.testng.AssertJUnit.assertTrue;
27 import jalview.commands.EditCommand.Action;
28 import jalview.commands.EditCommand.Edit;
29 import jalview.datamodel.Alignment;
30 import jalview.datamodel.AlignmentI;
31 import jalview.datamodel.Sequence;
32 import jalview.datamodel.SequenceFeature;
33 import jalview.datamodel.SequenceI;
34 import jalview.datamodel.features.SequenceFeatures;
35 import jalview.gui.JvOptionPane;
37 import java.util.Collections;
38 import java.util.Comparator;
39 import java.util.List;
42 import org.testng.Assert;
43 import org.testng.annotations.BeforeClass;
44 import org.testng.annotations.BeforeMethod;
45 import org.testng.annotations.Test;
48 * Unit tests for EditCommand
53 public class EditCommandTest
56 * compute n(n+1)/2 e.g.
57 * func(5) = 5 + 4 + 3 + 2 + 1 = 15
59 private static int func(int i)
61 return i * (i + 1) / 2;
64 @BeforeClass(alwaysRun = true)
65 public void setUpJvOptionPane()
67 JvOptionPane.setInteractiveMode(false);
68 JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
71 private EditCommand testee;
73 private SequenceI[] seqs;
77 @BeforeMethod(alwaysRun = true)
80 testee = new EditCommand();
81 seqs = new SequenceI[4];
82 seqs[0] = new Sequence("seq0", "abcdefghjk");
83 seqs[0].setDatasetSequence(new Sequence("seq0ds", "abcdefghjk"));
84 seqs[1] = new Sequence("seq1", "fghjklmnopq");
85 seqs[1].setDatasetSequence(new Sequence("seq1ds", "fghjklmnopq"));
86 seqs[2] = new Sequence("seq2", "qrstuvwxyz");
87 seqs[2].setDatasetSequence(new Sequence("seq2ds", "qrstuvwxyz"));
88 seqs[3] = new Sequence("seq3", "1234567890");
89 seqs[3].setDatasetSequence(new Sequence("seq3ds", "1234567890"));
90 al = new Alignment(seqs);
91 al.setGapCharacter('?');
95 * Test inserting gap characters
97 @Test(groups = { "Functional" })
98 public void testAppendEdit_insertGap()
100 // set a non-standard gap character to prove it is actually used
101 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 3, al, true);
102 assertEquals("abcd???efghjk", seqs[0].getSequenceAsString());
103 assertEquals("fghj???klmnopq", seqs[1].getSequenceAsString());
104 assertEquals("qrst???uvwxyz", seqs[2].getSequenceAsString());
105 assertEquals("1234???567890", seqs[3].getSequenceAsString());
107 // todo: test for handling out of range positions?
111 * Test deleting characters from sequences. Note the deleteGap() action does
112 * not check that only gap characters are being removed.
114 @Test(groups = { "Functional" })
115 public void testAppendEdit_deleteGap()
117 testee.appendEdit(Action.DELETE_GAP, seqs, 4, 3, al, true);
118 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
119 assertEquals("fghjnopq", seqs[1].getSequenceAsString());
120 assertEquals("qrstxyz", seqs[2].getSequenceAsString());
121 assertEquals("1234890", seqs[3].getSequenceAsString());
125 * Test a cut action. The command should store the cut characters to support
128 @Test(groups = { "Functional" })
129 public void testCut()
131 Edit ec = testee.new Edit(Action.CUT, seqs, 4, 3, al);
132 EditCommand.cut(ec, new AlignmentI[] { al });
133 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
134 assertEquals("fghjnopq", seqs[1].getSequenceAsString());
135 assertEquals("qrstxyz", seqs[2].getSequenceAsString());
136 assertEquals("1234890", seqs[3].getSequenceAsString());
138 assertEquals("efg", new String(ec.string[0]));
139 assertEquals("klm", new String(ec.string[1]));
140 assertEquals("uvw", new String(ec.string[2]));
141 assertEquals("567", new String(ec.string[3]));
142 // TODO: case where whole sequence is deleted as nothing left; etc
146 * Test a Paste action, where this adds sequences to an alignment.
148 @Test(groups = { "Functional" }, enabled = false)
149 // TODO fix so it works
150 public void testPaste_addToAlignment()
152 SequenceI[] newSeqs = new SequenceI[2];
153 newSeqs[0] = new Sequence("newseq0", "ACEFKL");
154 newSeqs[1] = new Sequence("newseq1", "JWMPDH");
156 Edit ec = testee.new Edit(Action.PASTE, newSeqs, 0, al.getWidth(), al);
157 EditCommand.paste(ec, new AlignmentI[] { al });
158 assertEquals(6, al.getSequences().size());
159 assertEquals("1234567890", seqs[3].getSequenceAsString());
160 assertEquals("ACEFKL", seqs[4].getSequenceAsString());
161 assertEquals("JWMPDH", seqs[5].getSequenceAsString());
165 * Test insertGap followed by undo command
167 @Test(groups = { "Functional" })
168 public void testUndo_insertGap()
170 // Edit ec = testee.new Edit(Action.INSERT_GAP, seqs, 4, 3, '?');
171 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 3, al, true);
172 // check something changed
173 assertEquals("abcd???efghjk", seqs[0].getSequenceAsString());
174 testee.undoCommand(new AlignmentI[] { al });
175 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
176 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
177 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
178 assertEquals("1234567890", seqs[3].getSequenceAsString());
182 * Test deleteGap followed by undo command
184 @Test(groups = { "Functional" })
185 public void testUndo_deleteGap()
187 testee.appendEdit(Action.DELETE_GAP, seqs, 4, 3, al, true);
188 // check something changed
189 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
190 testee.undoCommand(new AlignmentI[] { al });
191 // deleteGap doesn't 'remember' deleted characters, only gaps get put back
192 assertEquals("abcd???hjk", seqs[0].getSequenceAsString());
193 assertEquals("fghj???nopq", seqs[1].getSequenceAsString());
194 assertEquals("qrst???xyz", seqs[2].getSequenceAsString());
195 assertEquals("1234???890", seqs[3].getSequenceAsString());
199 * Test several commands followed by an undo command
201 @Test(groups = { "Functional" })
202 public void testUndo_multipleCommands()
204 // delete positions 3/4/5 (counting from 1)
205 testee.appendEdit(Action.DELETE_GAP, seqs, 2, 3, al, true);
206 assertEquals("abfghjk", seqs[0].getSequenceAsString());
207 assertEquals("1267890", seqs[3].getSequenceAsString());
209 // insert 2 gaps after the second residue
210 testee.appendEdit(Action.INSERT_GAP, seqs, 2, 2, al, true);
211 assertEquals("ab??fghjk", seqs[0].getSequenceAsString());
212 assertEquals("12??67890", seqs[3].getSequenceAsString());
214 // delete positions 4/5/6
215 testee.appendEdit(Action.DELETE_GAP, seqs, 3, 3, al, true);
216 assertEquals("ab?hjk", seqs[0].getSequenceAsString());
217 assertEquals("12?890", seqs[3].getSequenceAsString());
219 // undo edit commands
220 testee.undoCommand(new AlignmentI[] { al });
221 assertEquals("ab?????hjk", seqs[0].getSequenceAsString());
222 assertEquals("12?????890", seqs[3].getSequenceAsString());
226 * Unit test for JAL-1594 bug: click and drag sequence right to insert gaps -
227 * undo did not remove them all.
229 @Test(groups = { "Functional" })
230 public void testUndo_multipleInsertGaps()
232 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 1, al, true);
233 testee.appendEdit(Action.INSERT_GAP, seqs, 5, 1, al, true);
234 testee.appendEdit(Action.INSERT_GAP, seqs, 6, 1, al, true);
236 // undo edit commands
237 testee.undoCommand(new AlignmentI[] { al });
238 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
239 assertEquals("1234567890", seqs[3].getSequenceAsString());
244 * Test cut followed by undo command
246 @Test(groups = { "Functional" })
247 public void testUndo_cut()
249 testee.appendEdit(Action.CUT, seqs, 4, 3, al, true);
250 // check something changed
251 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
252 testee.undoCommand(new AlignmentI[] { al });
253 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
254 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
255 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
256 assertEquals("1234567890", seqs[3].getSequenceAsString());
260 * Test the replace command (used to manually edit a sequence)
262 @Test(groups = { "Functional" })
263 public void testReplace()
265 // seem to need a dataset sequence on the edited sequence here
266 seqs[1].createDatasetSequence();
267 new EditCommand("", Action.REPLACE, "ZXY", new SequenceI[] { seqs[1] },
269 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
270 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
271 assertEquals("1234567890", seqs[3].getSequenceAsString());
272 seqs[1] = new Sequence("seq1", "fghjZXYnopq");
276 * Test that the addEdit command correctly merges insert gap commands when
279 @Test(groups = { "Functional" })
280 public void testAddEdit_multipleInsertGap()
283 * 3 insert gap in a row (aka mouse drag right):
285 Edit e = new EditCommand().new Edit(Action.INSERT_GAP,
286 new SequenceI[] { seqs[0] }, 1, 1, al);
288 SequenceI edited = new Sequence("seq0", "a?bcdefghjk");
289 edited.setDatasetSequence(seqs[0].getDatasetSequence());
290 e = new EditCommand().new Edit(Action.INSERT_GAP,
291 new SequenceI[] { edited }, 2, 1, al);
293 edited = new Sequence("seq0", "a??bcdefghjk");
294 edited.setDatasetSequence(seqs[0].getDatasetSequence());
295 e = new EditCommand().new Edit(Action.INSERT_GAP,
296 new SequenceI[] { edited }, 3, 1, al);
298 assertEquals(1, testee.getSize());
299 assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
300 assertEquals(1, testee.getEdit(0).getPosition());
301 assertEquals(3, testee.getEdit(0).getNumber());
304 * Add a non-contiguous edit - should not be merged.
306 e = new EditCommand().new Edit(Action.INSERT_GAP,
307 new SequenceI[] { edited }, 5, 2, al);
309 assertEquals(2, testee.getSize());
310 assertEquals(5, testee.getEdit(1).getPosition());
311 assertEquals(2, testee.getEdit(1).getNumber());
314 * Add a Delete after the Insert - should not be merged.
316 e = new EditCommand().new Edit(Action.DELETE_GAP,
317 new SequenceI[] { edited }, 6, 2, al);
319 assertEquals(3, testee.getSize());
320 assertEquals(Action.DELETE_GAP, testee.getEdit(2).getAction());
321 assertEquals(6, testee.getEdit(2).getPosition());
322 assertEquals(2, testee.getEdit(2).getNumber());
326 * Test that the addEdit command correctly merges delete gap commands when
329 @Test(groups = { "Functional" })
330 public void testAddEdit_multipleDeleteGap()
333 * 3 delete gap in a row (aka mouse drag left):
335 seqs[0].setSequence("a???bcdefghjk");
336 Edit e = new EditCommand().new Edit(Action.DELETE_GAP,
337 new SequenceI[] { seqs[0] }, 4, 1, al);
339 assertEquals(1, testee.getSize());
341 SequenceI edited = new Sequence("seq0", "a??bcdefghjk");
342 edited.setDatasetSequence(seqs[0].getDatasetSequence());
343 e = new EditCommand().new Edit(Action.DELETE_GAP,
344 new SequenceI[] { edited }, 3, 1, al);
346 assertEquals(1, testee.getSize());
348 edited = new Sequence("seq0", "a?bcdefghjk");
349 edited.setDatasetSequence(seqs[0].getDatasetSequence());
350 e = new EditCommand().new Edit(Action.DELETE_GAP,
351 new SequenceI[] { edited }, 2, 1, al);
353 assertEquals(1, testee.getSize());
354 assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
355 assertEquals(2, testee.getEdit(0).getPosition());
356 assertEquals(3, testee.getEdit(0).getNumber());
359 * Add a non-contiguous edit - should not be merged.
361 e = new EditCommand().new Edit(Action.DELETE_GAP,
362 new SequenceI[] { edited }, 2, 1, al);
364 assertEquals(2, testee.getSize());
365 assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
366 assertEquals(2, testee.getEdit(1).getPosition());
367 assertEquals(1, testee.getEdit(1).getNumber());
370 * Add an Insert after the Delete - should not be merged.
372 e = new EditCommand().new Edit(Action.INSERT_GAP,
373 new SequenceI[] { edited }, 1, 1, al);
375 assertEquals(3, testee.getSize());
376 assertEquals(Action.INSERT_GAP, testee.getEdit(2).getAction());
377 assertEquals(1, testee.getEdit(2).getPosition());
378 assertEquals(1, testee.getEdit(2).getNumber());
382 * Test that the addEdit command correctly handles 'remove gaps' edits for the
383 * case when they appear contiguous but are acting on different sequences.
384 * They should not be merged.
386 @Test(groups = { "Functional" })
387 public void testAddEdit_removeAllGaps()
389 seqs[0].setSequence("a???bcdefghjk");
390 Edit e = new EditCommand().new Edit(Action.DELETE_GAP,
391 new SequenceI[] { seqs[0] }, 4, 1, al);
394 seqs[1].setSequence("f??ghjklmnopq");
395 Edit e2 = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
396 { seqs[1] }, 3, 1, al);
398 assertEquals(2, testee.getSize());
399 assertSame(e, testee.getEdit(0));
400 assertSame(e2, testee.getEdit(1));
404 * Test that the addEdit command correctly merges insert gap commands acting
405 * on a multi-sequence selection.
407 @Test(groups = { "Functional" })
408 public void testAddEdit_groupInsertGaps()
411 * 2 insert gap in a row (aka mouse drag right), on two sequences:
413 Edit e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] {
414 seqs[0], seqs[1] }, 1, 1, al);
416 SequenceI seq1edited = new Sequence("seq0", "a?bcdefghjk");
417 seq1edited.setDatasetSequence(seqs[0].getDatasetSequence());
418 SequenceI seq2edited = new Sequence("seq1", "f?ghjklmnopq");
419 seq2edited.setDatasetSequence(seqs[1].getDatasetSequence());
420 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] {
421 seq1edited, seq2edited }, 2, 1, al);
424 assertEquals(1, testee.getSize());
425 assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
426 assertEquals(1, testee.getEdit(0).getPosition());
427 assertEquals(2, testee.getEdit(0).getNumber());
428 assertEquals(seqs[0].getDatasetSequence(), testee.getEdit(0)
429 .getSequences()[0].getDatasetSequence());
430 assertEquals(seqs[1].getDatasetSequence(), testee.getEdit(0)
431 .getSequences()[1].getDatasetSequence());
435 * Test for 'undoing' a series of gap insertions.
437 * <li>Start: ABCDEF insert 2 at pos 1</li>
438 * <li>next: A--BCDEF insert 1 at pos 4</li>
439 * <li>next: A--B-CDEF insert 2 at pos 0</li>
440 * <li>last: --A--B-CDEF</li>
443 @Test(groups = { "Functional" })
444 public void testPriorState_multipleInserts()
446 EditCommand command = new EditCommand();
447 SequenceI seq = new Sequence("", "--A--B-CDEF");
448 SequenceI ds = new Sequence("", "ABCDEF");
449 seq.setDatasetSequence(ds);
450 SequenceI[] sqs = new SequenceI[] { seq };
451 Edit e = command.new Edit(Action.INSERT_GAP, sqs, 1, 2, '-');
453 e = command.new Edit(Action.INSERT_GAP, sqs, 4, 1, '-');
455 e = command.new Edit(Action.INSERT_GAP, sqs, 0, 2, '-');
458 Map<SequenceI, SequenceI> unwound = command.priorState(false);
459 assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString());
463 * Test for 'undoing' a series of gap deletions.
465 * <li>Start: A-B-C delete 1 at pos 1</li>
466 * <li>Next: AB-C delete 1 at pos 2</li>
470 @Test(groups = { "Functional" })
471 public void testPriorState_removeAllGaps()
473 EditCommand command = new EditCommand();
474 SequenceI seq = new Sequence("", "ABC");
475 SequenceI ds = new Sequence("", "ABC");
476 seq.setDatasetSequence(ds);
477 SequenceI[] sqs = new SequenceI[] { seq };
478 Edit e = command.new Edit(Action.DELETE_GAP, sqs, 1, 1, '-');
480 e = command.new Edit(Action.DELETE_GAP, sqs, 2, 1, '-');
483 Map<SequenceI, SequenceI> unwound = command.priorState(false);
484 assertEquals("A-B-C", unwound.get(ds).getSequenceAsString());
488 * Test for 'undoing' a single delete edit.
490 @Test(groups = { "Functional" })
491 public void testPriorState_singleDelete()
493 EditCommand command = new EditCommand();
494 SequenceI seq = new Sequence("", "ABCDEF");
495 SequenceI ds = new Sequence("", "ABCDEF");
496 seq.setDatasetSequence(ds);
497 SequenceI[] sqs = new SequenceI[] { seq };
498 Edit e = command.new Edit(Action.DELETE_GAP, sqs, 2, 2, '-');
501 Map<SequenceI, SequenceI> unwound = command.priorState(false);
502 assertEquals("AB--CDEF", unwound.get(ds).getSequenceAsString());
506 * Test 'undoing' a single gap insertion edit command.
508 @Test(groups = { "Functional" })
509 public void testPriorState_singleInsert()
511 EditCommand command = new EditCommand();
512 SequenceI seq = new Sequence("", "AB---CDEF");
513 SequenceI ds = new Sequence("", "ABCDEF");
514 seq.setDatasetSequence(ds);
515 SequenceI[] sqs = new SequenceI[] { seq };
516 Edit e = command.new Edit(Action.INSERT_GAP, sqs, 2, 3, '-');
519 Map<SequenceI, SequenceI> unwound = command.priorState(false);
520 SequenceI prior = unwound.get(ds);
521 assertEquals("ABCDEF", prior.getSequenceAsString());
522 assertEquals(1, prior.getStart());
523 assertEquals(6, prior.getEnd());
527 * Test 'undoing' a single gap insertion edit command, on a sequence whose
528 * start residue is other than 1
530 @Test(groups = { "Functional" })
531 public void testPriorState_singleInsertWithOffset()
533 EditCommand command = new EditCommand();
534 SequenceI seq = new Sequence("", "AB---CDEF", 8, 13);
535 // SequenceI ds = new Sequence("", "ABCDEF", 8, 13);
536 // seq.setDatasetSequence(ds);
537 seq.createDatasetSequence();
538 SequenceI[] sqs = new SequenceI[] { seq };
539 Edit e = command.new Edit(Action.INSERT_GAP, sqs, 2, 3, '-');
542 Map<SequenceI, SequenceI> unwound = command.priorState(false);
543 SequenceI prior = unwound.get(seq.getDatasetSequence());
544 assertEquals("ABCDEF", prior.getSequenceAsString());
545 assertEquals(8, prior.getStart());
546 assertEquals(13, prior.getEnd());
550 * Test that mimics 'remove all gaps' action. This generates delete gap edits
551 * for contiguous gaps in each sequence separately.
553 @Test(groups = { "Functional" })
554 public void testPriorState_removeGapsMultipleSeqs()
556 EditCommand command = new EditCommand();
557 String original1 = "--ABC-DEF";
558 String original2 = "FG-HI--J";
559 String original3 = "M-NOPQ";
562 * Two edits for the first sequence
564 SequenceI seq = new Sequence("", "ABC-DEF");
565 SequenceI ds1 = new Sequence("", "ABCDEF");
566 seq.setDatasetSequence(ds1);
567 SequenceI[] sqs = new SequenceI[] { seq };
568 Edit e = command.new Edit(Action.DELETE_GAP, sqs, 0, 2, '-');
570 seq = new Sequence("", "ABCDEF");
571 seq.setDatasetSequence(ds1);
572 sqs = new SequenceI[] { seq };
573 e = command.new Edit(Action.DELETE_GAP, sqs, 3, 1, '-');
577 * Two edits for the second sequence
579 seq = new Sequence("", "FGHI--J");
580 SequenceI ds2 = new Sequence("", "FGHIJ");
581 seq.setDatasetSequence(ds2);
582 sqs = new SequenceI[] { seq };
583 e = command.new Edit(Action.DELETE_GAP, sqs, 2, 1, '-');
585 seq = new Sequence("", "FGHIJ");
586 seq.setDatasetSequence(ds2);
587 sqs = new SequenceI[] { seq };
588 e = command.new Edit(Action.DELETE_GAP, sqs, 4, 2, '-');
592 * One edit for the third sequence.
594 seq = new Sequence("", "MNOPQ");
595 SequenceI ds3 = new Sequence("", "MNOPQ");
596 seq.setDatasetSequence(ds3);
597 sqs = new SequenceI[] { seq };
598 e = command.new Edit(Action.DELETE_GAP, sqs, 1, 1, '-');
601 Map<SequenceI, SequenceI> unwound = command.priorState(false);
602 assertEquals(original1, unwound.get(ds1).getSequenceAsString());
603 assertEquals(original2, unwound.get(ds2).getSequenceAsString());
604 assertEquals(original3, unwound.get(ds3).getSequenceAsString());
608 * Test that mimics 'remove all gapped columns' action. This generates a
609 * series Delete Gap edits that each act on all sequences that share a gapped
612 @Test(groups = { "Functional" })
613 public void testPriorState_removeGappedCols()
615 EditCommand command = new EditCommand();
616 String original1 = "--ABC--DEF";
617 String original2 = "-G-HI--J";
618 String original3 = "-M-NO--PQ";
621 * First edit deletes the first column.
623 SequenceI seq1 = new Sequence("", "-ABC--DEF");
624 SequenceI ds1 = new Sequence("", "ABCDEF");
625 seq1.setDatasetSequence(ds1);
626 SequenceI seq2 = new Sequence("", "G-HI--J");
627 SequenceI ds2 = new Sequence("", "GHIJ");
628 seq2.setDatasetSequence(ds2);
629 SequenceI seq3 = new Sequence("", "M-NO--PQ");
630 SequenceI ds3 = new Sequence("", "MNOPQ");
631 seq3.setDatasetSequence(ds3);
632 SequenceI[] sqs = new SequenceI[] { seq1, seq2, seq3 };
633 Edit e = command.new Edit(Action.DELETE_GAP, sqs, 0, 1, '-');
637 * Second edit deletes what is now columns 4 and 5.
639 seq1 = new Sequence("", "-ABCDEF");
640 seq1.setDatasetSequence(ds1);
641 seq2 = new Sequence("", "G-HIJ");
642 seq2.setDatasetSequence(ds2);
643 seq3 = new Sequence("", "M-NOPQ");
644 seq3.setDatasetSequence(ds3);
645 sqs = new SequenceI[] { seq1, seq2, seq3 };
646 e = command.new Edit(Action.DELETE_GAP, sqs, 4, 2, '-');
649 Map<SequenceI, SequenceI> unwound = command.priorState(false);
650 assertEquals(original1, unwound.get(ds1).getSequenceAsString());
651 assertEquals(original2, unwound.get(ds2).getSequenceAsString());
652 assertEquals(original3, unwound.get(ds3).getSequenceAsString());
653 assertEquals(ds1, unwound.get(ds1).getDatasetSequence());
654 assertEquals(ds2, unwound.get(ds2).getDatasetSequence());
655 assertEquals(ds3, unwound.get(ds3).getDatasetSequence());
659 * Test a cut action's relocation of sequence features
661 @Test(groups = { "Functional" })
662 public void testCut_withFeatures()
665 * create sequence features before, after and overlapping
666 * a cut of columns/residues 4-7
668 SequenceI seq0 = seqs[0]; // abcdefghjk/1-10
669 seq0.addSequenceFeature(new SequenceFeature("before", "", 1, 3, 0f,
671 seq0.addSequenceFeature(new SequenceFeature("overlap left", "", 2, 6,
673 seq0.addSequenceFeature(new SequenceFeature("internal", "", 5, 6, 0f,
675 seq0.addSequenceFeature(new SequenceFeature("overlap right", "", 7, 8,
677 seq0.addSequenceFeature(new SequenceFeature("after", "", 8, 10, 0f,
681 * add some contact features
683 SequenceFeature internalContact = new SequenceFeature("disulphide bond", "", 5,
685 seq0.addSequenceFeature(internalContact); // should get deleted
686 SequenceFeature overlapLeftContact = new SequenceFeature(
687 "disulphide bond", "", 2, 6, 0f, null);
688 seq0.addSequenceFeature(overlapLeftContact); // should get deleted
689 SequenceFeature overlapRightContact = new SequenceFeature(
690 "disulphide bond", "", 5, 8, 0f, null);
691 seq0.addSequenceFeature(overlapRightContact); // should get deleted
692 SequenceFeature spanningContact = new SequenceFeature(
693 "disulphide bond", "", 2, 9, 0f, null);
694 seq0.addSequenceFeature(spanningContact); // should get shortened 3'
697 * cut columns 3-6 (base 0), residues d-g 4-7
699 Edit ec = testee.new Edit(Action.CUT, seqs, 3, 4, al); // cols 3-6 base 0
700 EditCommand.cut(ec, new AlignmentI[] { al });
702 List<SequenceFeature> sfs = seq0.getSequenceFeatures();
703 SequenceFeatures.sortFeatures(sfs, true);
705 assertEquals(5, sfs.size()); // features internal to cut were deleted
706 SequenceFeature sf = sfs.get(0);
707 assertEquals("before", sf.getType());
708 assertEquals(1, sf.getBegin());
709 assertEquals(3, sf.getEnd());
711 assertEquals("disulphide bond", sf.getType());
712 assertEquals(2, sf.getBegin());
713 assertEquals(5, sf.getEnd()); // truncated by cut
715 assertEquals("overlap left", sf.getType());
716 assertEquals(2, sf.getBegin());
717 assertEquals(3, sf.getEnd()); // truncated by cut
719 assertEquals("after", sf.getType());
720 assertEquals(4, sf.getBegin()); // shifted left by cut
721 assertEquals(6, sf.getEnd()); // shifted left by cut
723 assertEquals("overlap right", sf.getType());
724 assertEquals(4, sf.getBegin()); // shifted left by cut
725 assertEquals(4, sf.getEnd()); // truncated by cut
729 * Test a cut action's relocation of sequence features, with full coverage of
730 * all possible feature and cut locations for a 5-position ungapped sequence
732 @Test(groups = { "Functional" })
733 public void testCut_withFeatures_exhaustive()
736 * create a sequence features on each subrange of 1-5
738 SequenceI seq0 = new Sequence("seq", "ABCDE");
739 AlignmentI alignment = new Alignment(new SequenceI[] { seq0 });
740 alignment.setDataset(null);
741 for (int from = 1; from <= seq0.getLength(); from++)
743 for (int to = from; to <= seq0.getLength(); to++)
745 String desc = String.format("%d-%d", from, to);
746 SequenceFeature sf = new SequenceFeature("test", desc, from, to,
748 sf.setValue("from", Integer.valueOf(from));
749 sf.setValue("to", Integer.valueOf(to));
750 seq0.addSequenceFeature(sf);
754 List<SequenceFeature> sfs = seq0.getSequenceFeatures();
755 assertEquals(func(5), sfs.size());
758 * now perform all possible cuts of subranges of 1-5 (followed by Undo)
759 * and validate the resulting remaining sequence features!
761 SequenceI[] sqs = new SequenceI[] { seq0 };
763 for (int from = 0; from < seq0.getLength(); from++)
765 for (int to = from; to < seq0.getLength(); to++)
767 testee.appendEdit(Action.CUT, sqs, from, (to - from + 1),
770 sfs = seq0.getSequenceFeatures();
773 * sort just for ease of inspection in the debugger
775 Collections.sort(sfs, new Comparator<SequenceFeature>()
779 public int compare(SequenceFeature o1, SequenceFeature o2)
781 return o1.getDescription().compareTo(o2.getDescription());
786 * confirm the number of features has reduced by the
787 * number of features within the cut region i.e. by
788 * func(length of cut)
790 String msg = String.format("Cut %d-%d ", from + 1, to + 1);
793 // all columns were cut
794 assertTrue(sfs.isEmpty());
798 // failure in checkFeatureRelocation is more informative!
799 assertEquals(msg + "wrong number of features left", func(5)
800 - func(to - from + 1), sfs.size());
804 * inspect individual features
806 for (SequenceFeature sf : sfs)
808 checkFeatureRelocation(sf, from + 1, to + 1, from > 0);
812 * undo ready for next cut
814 testee.undoCommand(new AlignmentI[] { alignment });
815 sfs = seq0.getSequenceFeatures();
816 assertEquals("After undo of " + msg, func(5), sfs.size());
817 verifyUndo(from, to, sfs);
823 * Check that after Undo, every feature has start/end that match its original
824 * "start" and "end" properties
830 protected void verifyUndo(int from, int to, List<SequenceFeature> sfs)
832 for (SequenceFeature sf : sfs)
834 final int oldFrom = ((Integer) sf.getValue("from")).intValue();
835 final int oldTo = ((Integer) sf.getValue("to")).intValue();
836 String msg = String.format(
837 "Undo cut of [%d-%d], feature at [%d-%d] ", from + 1, to + 1,
839 assertEquals(msg + "start", oldFrom, sf.getBegin());
840 assertEquals(msg + "end", oldTo, sf.getEnd());
845 * Helper method to check a feature has been correctly relocated after a cut
849 * start of cut (first residue cut)
851 * end of cut (last residue cut)
854 private void checkFeatureRelocation(SequenceFeature sf, int from, int to,
857 // TODO handle the gapped sequence case as well
858 int cutSize = to - from + 1;
859 final int oldFrom = ((Integer) sf.getValue("from")).intValue();
860 final int oldTo = ((Integer) sf.getValue("to")).intValue();
862 String msg = String.format(
863 "Feature %s relocated to %d-%d after cut of %d-%d",
864 sf.getDescription(), sf.getBegin(), sf.getEnd(), from, to);
867 // before cut region so unchanged
868 assertEquals("1: " + msg, oldFrom, sf.getBegin());
869 assertEquals("2: " + msg, oldTo, sf.getEnd());
871 else if (oldFrom > to)
873 // follows cut region - shift by size of cut
874 assertEquals("3: " + msg, newDataset ? oldFrom - cutSize : oldFrom,
876 assertEquals("4: " + msg, newDataset ? oldTo - cutSize : oldTo,
879 else if (oldFrom < from && oldTo > to)
881 // feature encloses cut region - shrink it right
882 assertEquals("5: " + msg, oldFrom, sf.getBegin());
883 assertEquals("6: " + msg, oldTo - cutSize, sf.getEnd());
885 else if (oldFrom < from)
887 // feature overlaps left side of cut region - truncated right
888 assertEquals("7: " + msg, from - 1, sf.getEnd());
892 // feature overlaps right side of cut region - truncated left
893 assertEquals("8: " + msg, newDataset ? from : to + 1, sf.getBegin());
894 assertEquals("9: " + msg, newDataset ? from + oldTo - to - 1 : oldTo,
899 // feature internal to cut - should have been deleted!
900 Assert.fail(msg + " - should have been deleted");
905 * Test a cut action's relocation of sequence features
907 @Test(groups = { "Functional" })
908 public void testCut_withFeatures5prime()
910 SequenceI seq0 = new Sequence("seq/8-11", "A-BCC");
911 seq0.createDatasetSequence();
912 assertEquals(8, seq0.getStart());
913 seq0.addSequenceFeature(new SequenceFeature("", "", 10, 11, 0f,
915 SequenceI[] seqsArray = new SequenceI[] { seq0 };
916 AlignmentI alignment = new Alignment(seqsArray);
919 * cut columns of A-B; same dataset sequence is retained, aligned sequence
922 Edit ec = testee.new Edit(Action.CUT, seqsArray, 0, 3, alignment);
923 EditCommand.cut(ec, new AlignmentI[] { alignment });
926 * feature on CC(10-11) should still be on CC(10-11)
928 assertSame(seq0, alignment.getSequenceAt(0));
929 assertEquals(10, seq0.getStart());
930 List<SequenceFeature> sfs = seq0.getSequenceFeatures();
931 assertEquals(1, sfs.size());
932 SequenceFeature sf = sfs.get(0);
933 assertEquals(10, sf.getBegin());
934 assertEquals(11, sf.getEnd());
936 // TODO add further cases including Undo - see JAL-2541