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
55 private static Comparator<SequenceFeature> BY_DESCRIPTION = new Comparator<SequenceFeature>()
59 public int compare(SequenceFeature o1, SequenceFeature o2)
61 return o1.getDescription().compareTo(o2.getDescription());
65 private EditCommand testee;
67 private SequenceI[] seqs;
72 * compute n(n+1)/2 e.g.
73 * func(5) = 5 + 4 + 3 + 2 + 1 = 15
75 private static int func(int i)
77 return i * (i + 1) / 2;
80 @BeforeClass(alwaysRun = true)
81 public void setUpJvOptionPane()
83 JvOptionPane.setInteractiveMode(false);
84 JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
87 @BeforeMethod(alwaysRun = true)
90 testee = new EditCommand();
91 seqs = new SequenceI[4];
92 seqs[0] = new Sequence("seq0", "abcdefghjk");
93 seqs[0].setDatasetSequence(new Sequence("seq0ds", "abcdefghjk"));
94 seqs[1] = new Sequence("seq1", "fghjklmnopq");
95 seqs[1].setDatasetSequence(new Sequence("seq1ds", "fghjklmnopq"));
96 seqs[2] = new Sequence("seq2", "qrstuvwxyz");
97 seqs[2].setDatasetSequence(new Sequence("seq2ds", "qrstuvwxyz"));
98 seqs[3] = new Sequence("seq3", "1234567890");
99 seqs[3].setDatasetSequence(new Sequence("seq3ds", "1234567890"));
100 al = new Alignment(seqs);
101 al.setGapCharacter('?');
105 * Test inserting gap characters
107 @Test(groups = { "Functional" })
108 public void testAppendEdit_insertGap()
110 // set a non-standard gap character to prove it is actually used
111 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 3, al, true);
112 assertEquals("abcd???efghjk", seqs[0].getSequenceAsString());
113 assertEquals("fghj???klmnopq", seqs[1].getSequenceAsString());
114 assertEquals("qrst???uvwxyz", seqs[2].getSequenceAsString());
115 assertEquals("1234???567890", seqs[3].getSequenceAsString());
117 // todo: test for handling out of range positions?
121 * Test deleting characters from sequences. Note the deleteGap() action does
122 * not check that only gap characters are being removed.
124 @Test(groups = { "Functional" })
125 public void testAppendEdit_deleteGap()
127 testee.appendEdit(Action.DELETE_GAP, seqs, 4, 3, al, true);
128 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
129 assertEquals("fghjnopq", seqs[1].getSequenceAsString());
130 assertEquals("qrstxyz", seqs[2].getSequenceAsString());
131 assertEquals("1234890", seqs[3].getSequenceAsString());
135 * Test a cut action. The command should store the cut characters to support
138 @Test(groups = { "Functional" })
139 public void testCut()
141 Edit ec = testee.new Edit(Action.CUT, seqs, 4, 3, al);
142 EditCommand.cut(ec, new AlignmentI[] { al });
143 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
144 assertEquals("fghjnopq", seqs[1].getSequenceAsString());
145 assertEquals("qrstxyz", seqs[2].getSequenceAsString());
146 assertEquals("1234890", seqs[3].getSequenceAsString());
148 assertEquals("efg", new String(ec.string[0]));
149 assertEquals("klm", new String(ec.string[1]));
150 assertEquals("uvw", new String(ec.string[2]));
151 assertEquals("567", new String(ec.string[3]));
152 // TODO: case where whole sequence is deleted as nothing left; etc
156 * Test a Paste action, where this adds sequences to an alignment.
158 @Test(groups = { "Functional" }, enabled = false)
159 // TODO fix so it works
160 public void testPaste_addToAlignment()
162 SequenceI[] newSeqs = new SequenceI[2];
163 newSeqs[0] = new Sequence("newseq0", "ACEFKL");
164 newSeqs[1] = new Sequence("newseq1", "JWMPDH");
166 Edit ec = testee.new Edit(Action.PASTE, newSeqs, 0, al.getWidth(), al);
167 EditCommand.paste(ec, new AlignmentI[] { al });
168 assertEquals(6, al.getSequences().size());
169 assertEquals("1234567890", seqs[3].getSequenceAsString());
170 assertEquals("ACEFKL", seqs[4].getSequenceAsString());
171 assertEquals("JWMPDH", seqs[5].getSequenceAsString());
175 * Test insertGap followed by undo command
177 @Test(groups = { "Functional" })
178 public void testUndo_insertGap()
180 // Edit ec = testee.new Edit(Action.INSERT_GAP, seqs, 4, 3, '?');
181 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 3, al, true);
182 // check something changed
183 assertEquals("abcd???efghjk", seqs[0].getSequenceAsString());
184 testee.undoCommand(new AlignmentI[] { al });
185 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
186 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
187 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
188 assertEquals("1234567890", seqs[3].getSequenceAsString());
192 * Test deleteGap followed by undo command
194 @Test(groups = { "Functional" })
195 public void testUndo_deleteGap()
197 testee.appendEdit(Action.DELETE_GAP, seqs, 4, 3, al, true);
198 // check something changed
199 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
200 testee.undoCommand(new AlignmentI[] { al });
201 // deleteGap doesn't 'remember' deleted characters, only gaps get put back
202 assertEquals("abcd???hjk", seqs[0].getSequenceAsString());
203 assertEquals("fghj???nopq", seqs[1].getSequenceAsString());
204 assertEquals("qrst???xyz", seqs[2].getSequenceAsString());
205 assertEquals("1234???890", seqs[3].getSequenceAsString());
209 * Test several commands followed by an undo command
211 @Test(groups = { "Functional" })
212 public void testUndo_multipleCommands()
214 // delete positions 3/4/5 (counting from 1)
215 testee.appendEdit(Action.DELETE_GAP, seqs, 2, 3, al, true);
216 assertEquals("abfghjk", seqs[0].getSequenceAsString());
217 assertEquals("1267890", seqs[3].getSequenceAsString());
219 // insert 2 gaps after the second residue
220 testee.appendEdit(Action.INSERT_GAP, seqs, 2, 2, al, true);
221 assertEquals("ab??fghjk", seqs[0].getSequenceAsString());
222 assertEquals("12??67890", seqs[3].getSequenceAsString());
224 // delete positions 4/5/6
225 testee.appendEdit(Action.DELETE_GAP, seqs, 3, 3, al, true);
226 assertEquals("ab?hjk", seqs[0].getSequenceAsString());
227 assertEquals("12?890", seqs[3].getSequenceAsString());
229 // undo edit commands
230 testee.undoCommand(new AlignmentI[] { al });
231 assertEquals("ab?????hjk", seqs[0].getSequenceAsString());
232 assertEquals("12?????890", seqs[3].getSequenceAsString());
236 * Unit test for JAL-1594 bug: click and drag sequence right to insert gaps -
237 * undo did not remove them all.
239 @Test(groups = { "Functional" })
240 public void testUndo_multipleInsertGaps()
242 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 1, al, true);
243 testee.appendEdit(Action.INSERT_GAP, seqs, 5, 1, al, true);
244 testee.appendEdit(Action.INSERT_GAP, seqs, 6, 1, al, true);
246 // undo edit commands
247 testee.undoCommand(new AlignmentI[] { al });
248 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
249 assertEquals("1234567890", seqs[3].getSequenceAsString());
254 * Test cut followed by undo command
256 @Test(groups = { "Functional" })
257 public void testUndo_cut()
259 testee.appendEdit(Action.CUT, seqs, 4, 3, al, true);
260 // check something changed
261 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
262 testee.undoCommand(new AlignmentI[] { al });
263 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
264 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
265 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
266 assertEquals("1234567890", seqs[3].getSequenceAsString());
270 * Test the replace command (used to manually edit a sequence)
272 @Test(groups = { "Functional" })
273 public void testReplace()
275 // seem to need a dataset sequence on the edited sequence here
276 seqs[1].createDatasetSequence();
277 new EditCommand("", Action.REPLACE, "ZXY", new SequenceI[] { seqs[1] },
279 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
280 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
281 assertEquals("1234567890", seqs[3].getSequenceAsString());
285 * Test that the addEdit command correctly merges insert gap commands when
288 @Test(groups = { "Functional" })
289 public void testAddEdit_multipleInsertGap()
292 * 3 insert gap in a row (aka mouse drag right):
294 Edit e = new EditCommand().new Edit(Action.INSERT_GAP,
295 new SequenceI[] { seqs[0] }, 1, 1, al);
297 SequenceI edited = new Sequence("seq0", "a?bcdefghjk");
298 edited.setDatasetSequence(seqs[0].getDatasetSequence());
299 e = new EditCommand().new Edit(Action.INSERT_GAP,
300 new SequenceI[] { edited }, 2, 1, al);
302 edited = new Sequence("seq0", "a??bcdefghjk");
303 edited.setDatasetSequence(seqs[0].getDatasetSequence());
304 e = new EditCommand().new Edit(Action.INSERT_GAP,
305 new SequenceI[] { edited }, 3, 1, al);
307 assertEquals(1, testee.getSize());
308 assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
309 assertEquals(1, testee.getEdit(0).getPosition());
310 assertEquals(3, testee.getEdit(0).getNumber());
313 * Add a non-contiguous edit - should not be merged.
315 e = new EditCommand().new Edit(Action.INSERT_GAP,
316 new SequenceI[] { edited }, 5, 2, al);
318 assertEquals(2, testee.getSize());
319 assertEquals(5, testee.getEdit(1).getPosition());
320 assertEquals(2, testee.getEdit(1).getNumber());
323 * Add a Delete after the Insert - should not be merged.
325 e = new EditCommand().new Edit(Action.DELETE_GAP,
326 new SequenceI[] { edited }, 6, 2, al);
328 assertEquals(3, testee.getSize());
329 assertEquals(Action.DELETE_GAP, testee.getEdit(2).getAction());
330 assertEquals(6, testee.getEdit(2).getPosition());
331 assertEquals(2, testee.getEdit(2).getNumber());
335 * Test that the addEdit command correctly merges delete gap commands when
338 @Test(groups = { "Functional" })
339 public void testAddEdit_multipleDeleteGap()
342 * 3 delete gap in a row (aka mouse drag left):
344 seqs[0].setSequence("a???bcdefghjk");
345 Edit e = new EditCommand().new Edit(Action.DELETE_GAP,
346 new SequenceI[] { seqs[0] }, 4, 1, al);
348 assertEquals(1, testee.getSize());
350 SequenceI edited = new Sequence("seq0", "a??bcdefghjk");
351 edited.setDatasetSequence(seqs[0].getDatasetSequence());
352 e = new EditCommand().new Edit(Action.DELETE_GAP,
353 new SequenceI[] { edited }, 3, 1, al);
355 assertEquals(1, testee.getSize());
357 edited = new Sequence("seq0", "a?bcdefghjk");
358 edited.setDatasetSequence(seqs[0].getDatasetSequence());
359 e = new EditCommand().new Edit(Action.DELETE_GAP,
360 new SequenceI[] { edited }, 2, 1, al);
362 assertEquals(1, testee.getSize());
363 assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
364 assertEquals(2, testee.getEdit(0).getPosition());
365 assertEquals(3, testee.getEdit(0).getNumber());
368 * Add a non-contiguous edit - should not be merged.
370 e = new EditCommand().new Edit(Action.DELETE_GAP,
371 new SequenceI[] { edited }, 2, 1, al);
373 assertEquals(2, testee.getSize());
374 assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
375 assertEquals(2, testee.getEdit(1).getPosition());
376 assertEquals(1, testee.getEdit(1).getNumber());
379 * Add an Insert after the Delete - should not be merged.
381 e = new EditCommand().new Edit(Action.INSERT_GAP,
382 new SequenceI[] { edited }, 1, 1, al);
384 assertEquals(3, testee.getSize());
385 assertEquals(Action.INSERT_GAP, testee.getEdit(2).getAction());
386 assertEquals(1, testee.getEdit(2).getPosition());
387 assertEquals(1, testee.getEdit(2).getNumber());
391 * Test that the addEdit command correctly handles 'remove gaps' edits for the
392 * case when they appear contiguous but are acting on different sequences.
393 * They should not be merged.
395 @Test(groups = { "Functional" })
396 public void testAddEdit_removeAllGaps()
398 seqs[0].setSequence("a???bcdefghjk");
399 Edit e = new EditCommand().new Edit(Action.DELETE_GAP,
400 new SequenceI[] { seqs[0] }, 4, 1, al);
403 seqs[1].setSequence("f??ghjklmnopq");
404 Edit e2 = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
405 { seqs[1] }, 3, 1, al);
407 assertEquals(2, testee.getSize());
408 assertSame(e, testee.getEdit(0));
409 assertSame(e2, testee.getEdit(1));
413 * Test that the addEdit command correctly merges insert gap commands acting
414 * on a multi-sequence selection.
416 @Test(groups = { "Functional" })
417 public void testAddEdit_groupInsertGaps()
420 * 2 insert gap in a row (aka mouse drag right), on two sequences:
422 Edit e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] {
423 seqs[0], seqs[1] }, 1, 1, al);
425 SequenceI seq1edited = new Sequence("seq0", "a?bcdefghjk");
426 seq1edited.setDatasetSequence(seqs[0].getDatasetSequence());
427 SequenceI seq2edited = new Sequence("seq1", "f?ghjklmnopq");
428 seq2edited.setDatasetSequence(seqs[1].getDatasetSequence());
429 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] {
430 seq1edited, seq2edited }, 2, 1, al);
433 assertEquals(1, testee.getSize());
434 assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
435 assertEquals(1, testee.getEdit(0).getPosition());
436 assertEquals(2, testee.getEdit(0).getNumber());
437 assertEquals(seqs[0].getDatasetSequence(), testee.getEdit(0)
438 .getSequences()[0].getDatasetSequence());
439 assertEquals(seqs[1].getDatasetSequence(), testee.getEdit(0)
440 .getSequences()[1].getDatasetSequence());
444 * Test for 'undoing' a series of gap insertions.
446 * <li>Start: ABCDEF insert 2 at pos 1</li>
447 * <li>next: A--BCDEF insert 1 at pos 4</li>
448 * <li>next: A--B-CDEF insert 2 at pos 0</li>
449 * <li>last: --A--B-CDEF</li>
452 @Test(groups = { "Functional" })
453 public void testPriorState_multipleInserts()
455 EditCommand command = new EditCommand();
456 SequenceI seq = new Sequence("", "--A--B-CDEF");
457 SequenceI ds = new Sequence("", "ABCDEF");
458 seq.setDatasetSequence(ds);
459 SequenceI[] sqs = new SequenceI[] { seq };
460 Edit e = command.new Edit(Action.INSERT_GAP, sqs, 1, 2, '-');
462 e = command.new Edit(Action.INSERT_GAP, sqs, 4, 1, '-');
464 e = command.new Edit(Action.INSERT_GAP, sqs, 0, 2, '-');
467 Map<SequenceI, SequenceI> unwound = command.priorState(false);
468 assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString());
472 * Test for 'undoing' a series of gap deletions.
474 * <li>Start: A-B-C delete 1 at pos 1</li>
475 * <li>Next: AB-C delete 1 at pos 2</li>
479 @Test(groups = { "Functional" })
480 public void testPriorState_removeAllGaps()
482 EditCommand command = new EditCommand();
483 SequenceI seq = new Sequence("", "ABC");
484 SequenceI ds = new Sequence("", "ABC");
485 seq.setDatasetSequence(ds);
486 SequenceI[] sqs = new SequenceI[] { seq };
487 Edit e = command.new Edit(Action.DELETE_GAP, sqs, 1, 1, '-');
489 e = command.new Edit(Action.DELETE_GAP, sqs, 2, 1, '-');
492 Map<SequenceI, SequenceI> unwound = command.priorState(false);
493 assertEquals("A-B-C", unwound.get(ds).getSequenceAsString());
497 * Test for 'undoing' a single delete edit.
499 @Test(groups = { "Functional" })
500 public void testPriorState_singleDelete()
502 EditCommand command = new EditCommand();
503 SequenceI seq = new Sequence("", "ABCDEF");
504 SequenceI ds = new Sequence("", "ABCDEF");
505 seq.setDatasetSequence(ds);
506 SequenceI[] sqs = new SequenceI[] { seq };
507 Edit e = command.new Edit(Action.DELETE_GAP, sqs, 2, 2, '-');
510 Map<SequenceI, SequenceI> unwound = command.priorState(false);
511 assertEquals("AB--CDEF", unwound.get(ds).getSequenceAsString());
515 * Test 'undoing' a single gap insertion edit command.
517 @Test(groups = { "Functional" })
518 public void testPriorState_singleInsert()
520 EditCommand command = new EditCommand();
521 SequenceI seq = new Sequence("", "AB---CDEF");
522 SequenceI ds = new Sequence("", "ABCDEF");
523 seq.setDatasetSequence(ds);
524 SequenceI[] sqs = new SequenceI[] { seq };
525 Edit e = command.new Edit(Action.INSERT_GAP, sqs, 2, 3, '-');
528 Map<SequenceI, SequenceI> unwound = command.priorState(false);
529 SequenceI prior = unwound.get(ds);
530 assertEquals("ABCDEF", prior.getSequenceAsString());
531 assertEquals(1, prior.getStart());
532 assertEquals(6, prior.getEnd());
536 * Test 'undoing' a single gap insertion edit command, on a sequence whose
537 * start residue is other than 1
539 @Test(groups = { "Functional" })
540 public void testPriorState_singleInsertWithOffset()
542 EditCommand command = new EditCommand();
543 SequenceI seq = new Sequence("", "AB---CDEF", 8, 13);
544 // SequenceI ds = new Sequence("", "ABCDEF", 8, 13);
545 // seq.setDatasetSequence(ds);
546 seq.createDatasetSequence();
547 SequenceI[] sqs = new SequenceI[] { seq };
548 Edit e = command.new Edit(Action.INSERT_GAP, sqs, 2, 3, '-');
551 Map<SequenceI, SequenceI> unwound = command.priorState(false);
552 SequenceI prior = unwound.get(seq.getDatasetSequence());
553 assertEquals("ABCDEF", prior.getSequenceAsString());
554 assertEquals(8, prior.getStart());
555 assertEquals(13, prior.getEnd());
559 * Test that mimics 'remove all gaps' action. This generates delete gap edits
560 * for contiguous gaps in each sequence separately.
562 @Test(groups = { "Functional" })
563 public void testPriorState_removeGapsMultipleSeqs()
565 EditCommand command = new EditCommand();
566 String original1 = "--ABC-DEF";
567 String original2 = "FG-HI--J";
568 String original3 = "M-NOPQ";
571 * Two edits for the first sequence
573 SequenceI seq = new Sequence("", "ABC-DEF");
574 SequenceI ds1 = new Sequence("", "ABCDEF");
575 seq.setDatasetSequence(ds1);
576 SequenceI[] sqs = new SequenceI[] { seq };
577 Edit e = command.new Edit(Action.DELETE_GAP, sqs, 0, 2, '-');
579 seq = new Sequence("", "ABCDEF");
580 seq.setDatasetSequence(ds1);
581 sqs = new SequenceI[] { seq };
582 e = command.new Edit(Action.DELETE_GAP, sqs, 3, 1, '-');
586 * Two edits for the second sequence
588 seq = new Sequence("", "FGHI--J");
589 SequenceI ds2 = new Sequence("", "FGHIJ");
590 seq.setDatasetSequence(ds2);
591 sqs = new SequenceI[] { seq };
592 e = command.new Edit(Action.DELETE_GAP, sqs, 2, 1, '-');
594 seq = new Sequence("", "FGHIJ");
595 seq.setDatasetSequence(ds2);
596 sqs = new SequenceI[] { seq };
597 e = command.new Edit(Action.DELETE_GAP, sqs, 4, 2, '-');
601 * One edit for the third sequence.
603 seq = new Sequence("", "MNOPQ");
604 SequenceI ds3 = new Sequence("", "MNOPQ");
605 seq.setDatasetSequence(ds3);
606 sqs = new SequenceI[] { seq };
607 e = command.new Edit(Action.DELETE_GAP, sqs, 1, 1, '-');
610 Map<SequenceI, SequenceI> unwound = command.priorState(false);
611 assertEquals(original1, unwound.get(ds1).getSequenceAsString());
612 assertEquals(original2, unwound.get(ds2).getSequenceAsString());
613 assertEquals(original3, unwound.get(ds3).getSequenceAsString());
617 * Test that mimics 'remove all gapped columns' action. This generates a
618 * series Delete Gap edits that each act on all sequences that share a gapped
621 @Test(groups = { "Functional" })
622 public void testPriorState_removeGappedCols()
624 EditCommand command = new EditCommand();
625 String original1 = "--ABC--DEF";
626 String original2 = "-G-HI--J";
627 String original3 = "-M-NO--PQ";
630 * First edit deletes the first column.
632 SequenceI seq1 = new Sequence("", "-ABC--DEF");
633 SequenceI ds1 = new Sequence("", "ABCDEF");
634 seq1.setDatasetSequence(ds1);
635 SequenceI seq2 = new Sequence("", "G-HI--J");
636 SequenceI ds2 = new Sequence("", "GHIJ");
637 seq2.setDatasetSequence(ds2);
638 SequenceI seq3 = new Sequence("", "M-NO--PQ");
639 SequenceI ds3 = new Sequence("", "MNOPQ");
640 seq3.setDatasetSequence(ds3);
641 SequenceI[] sqs = new SequenceI[] { seq1, seq2, seq3 };
642 Edit e = command.new Edit(Action.DELETE_GAP, sqs, 0, 1, '-');
646 * Second edit deletes what is now columns 4 and 5.
648 seq1 = new Sequence("", "-ABCDEF");
649 seq1.setDatasetSequence(ds1);
650 seq2 = new Sequence("", "G-HIJ");
651 seq2.setDatasetSequence(ds2);
652 seq3 = new Sequence("", "M-NOPQ");
653 seq3.setDatasetSequence(ds3);
654 sqs = new SequenceI[] { seq1, seq2, seq3 };
655 e = command.new Edit(Action.DELETE_GAP, sqs, 4, 2, '-');
658 Map<SequenceI, SequenceI> unwound = command.priorState(false);
659 assertEquals(original1, unwound.get(ds1).getSequenceAsString());
660 assertEquals(original2, unwound.get(ds2).getSequenceAsString());
661 assertEquals(original3, unwound.get(ds3).getSequenceAsString());
662 assertEquals(ds1, unwound.get(ds1).getDatasetSequence());
663 assertEquals(ds2, unwound.get(ds2).getDatasetSequence());
664 assertEquals(ds3, unwound.get(ds3).getDatasetSequence());
668 * Test a cut action's relocation of sequence features
670 @Test(groups = { "Functional" })
671 public void testCut_withFeatures()
674 * create sequence features before, after and overlapping
675 * a cut of columns/residues 4-7
677 SequenceI seq0 = seqs[0]; // abcdefghjk/1-10
678 seq0.addSequenceFeature(new SequenceFeature("before", "", 1, 3, 0f,
680 seq0.addSequenceFeature(new SequenceFeature("overlap left", "", 2, 6,
682 seq0.addSequenceFeature(new SequenceFeature("internal", "", 5, 6, 0f,
684 seq0.addSequenceFeature(new SequenceFeature("overlap right", "", 7, 8,
686 seq0.addSequenceFeature(new SequenceFeature("after", "", 8, 10, 0f,
690 * add some contact features
692 SequenceFeature internalContact = new SequenceFeature("disulphide bond", "", 5,
694 seq0.addSequenceFeature(internalContact); // should get deleted
695 SequenceFeature overlapLeftContact = new SequenceFeature(
696 "disulphide bond", "", 2, 6, 0f, null);
697 seq0.addSequenceFeature(overlapLeftContact); // should get deleted
698 SequenceFeature overlapRightContact = new SequenceFeature(
699 "disulphide bond", "", 5, 8, 0f, null);
700 seq0.addSequenceFeature(overlapRightContact); // should get deleted
701 SequenceFeature spanningContact = new SequenceFeature(
702 "disulphide bond", "", 2, 9, 0f, null);
703 seq0.addSequenceFeature(spanningContact); // should get shortened 3'
706 * cut columns 3-6 (base 0), residues d-g 4-7
708 Edit ec = testee.new Edit(Action.CUT, seqs, 3, 4, al); // cols 3-6 base 0
709 EditCommand.cut(ec, new AlignmentI[] { al });
711 List<SequenceFeature> sfs = seq0.getSequenceFeatures();
712 SequenceFeatures.sortFeatures(sfs, true);
714 assertEquals(5, sfs.size()); // features internal to cut were deleted
715 SequenceFeature sf = sfs.get(0);
716 assertEquals("before", sf.getType());
717 assertEquals(1, sf.getBegin());
718 assertEquals(3, sf.getEnd());
720 assertEquals("disulphide bond", sf.getType());
721 assertEquals(2, sf.getBegin());
722 assertEquals(5, sf.getEnd()); // truncated by cut
724 assertEquals("overlap left", sf.getType());
725 assertEquals(2, sf.getBegin());
726 assertEquals(3, sf.getEnd()); // truncated by cut
728 assertEquals("after", sf.getType());
729 assertEquals(4, sf.getBegin()); // shifted left by cut
730 assertEquals(6, sf.getEnd()); // shifted left by cut
732 assertEquals("overlap right", sf.getType());
733 assertEquals(4, sf.getBegin()); // shifted left by cut
734 assertEquals(4, sf.getEnd()); // truncated by cut
738 * Test a cut action's relocation of sequence features, with full coverage of
739 * all possible feature and cut locations for a 5-position ungapped sequence
741 @Test(groups = { "Functional" })
742 public void testCut_withFeatures_exhaustive()
745 * create a sequence features on each subrange of 1-5
747 SequenceI seq0 = new Sequence("seq", "ABCDE");
750 seq0.setStart(start);
752 AlignmentI alignment = new Alignment(new SequenceI[] { seq0 });
753 alignment.setDataset(null);
755 * create a new alignment with shared dataset sequence
757 AlignmentI copy = new Alignment(
759 { alignment.getDataset().getSequenceAt(0).deriveSequence() });
760 SequenceI copySeq0 = copy.getSequenceAt(0);
762 for (int from = start; from <= end; from++)
764 for (int to = from; to <= end; to++)
766 String desc = String.format("%d-%d", from, to);
767 SequenceFeature sf = new SequenceFeature("test", desc, from, to,
769 sf.setValue("from", Integer.valueOf(from));
770 sf.setValue("to", Integer.valueOf(to));
771 seq0.addSequenceFeature(sf);
775 List<SequenceFeature> sfs = seq0.getSequenceFeatures();
776 assertEquals(func(5), sfs.size());
777 assertEquals(sfs, copySeq0.getSequenceFeatures());
778 String copySequenceFeatures = copySeq0.getSequenceFeatures().toString();
780 * now perform all possible cuts of subranges of columns 1-5
781 * and validate the resulting remaining sequence features!
783 SequenceI[] sqs = new SequenceI[] { seq0 };
784 boolean checkDsSize = false;
786 for (int from = 0; from < seq0.getLength(); from++)
788 for (int to = from; to < seq0.getLength(); to++)
790 EditCommand ec = new EditCommand("Cut", Action.CUT, sqs, from, (to
791 - from + 1), alignment);
792 final String msg = String.format("Cut %d-%d ", from + 1, to + 1);
794 verifyCut(seq0, from, to, msg, start);
797 * verify copy alignment dataset sequence unaffected
799 assertEquals("Original dataset sequence was modified",
800 copySequenceFeatures,
801 copySeq0.getSequenceFeatures().toString());
805 * verify a new dataset sequence has appeared
807 assertEquals("Wrong Dataset size after cut",
808 copySeq0.getDatasetSequence() == seq0.getDatasetSequence()
811 alignment.getDataset().getHeight());
814 * undo and verify all restored
816 AlignmentI[] views = new AlignmentI[] { alignment };
817 ec.undoCommand(views);
818 sfs = seq0.getSequenceFeatures();
819 assertEquals("After undo of " + msg, func(5), sfs.size());
820 verifyUndo(from, to, sfs);
823 * verify copy alignment dataset sequence still unaffected
825 assertEquals("Original dataset sequence was modified",
826 copySequenceFeatures,
827 copySeq0.getSequenceFeatures().toString());
832 * verify dataset sequence has shrunk
834 assertEquals("Wrong Dataset size after cut",
835 copySeq0.getDatasetSequence() == seq0.getDatasetSequence()
838 alignment.getDataset().getHeight());
844 verifyCut(seq0, from, to, msg, start);
847 * verify copy alignment dataset sequence unaffected
849 assertEquals("Original dataset sequence was modified",
850 copySequenceFeatures,
851 copySeq0.getSequenceFeatures().toString());
856 * verify a new dataset sequence has appeared again
858 assertEquals("Wrong Dataset size after cut",
859 copySeq0.getDatasetSequence() == seq0.getDatasetSequence()
862 alignment.getDataset().getHeight());
865 * undo ready for next cut
867 ec.undoCommand(views);
870 * final verify that copy alignment dataset sequence is still unaffected
872 assertEquals("Original dataset sequence was modified",
873 copySequenceFeatures,
874 copySeq0.getSequenceFeatures().toString());
878 * and that dataset sequence has shrunk
880 assertEquals("Wrong Dataset size after cut",
881 copySeq0.getDatasetSequence() == seq0.getDatasetSequence()
884 alignment.getDataset().getHeight());
891 * Verify by inspection that the sequence features left on the sequence after
892 * a cut match the expected results. The trick to this is that we can parse
893 * each feature's original start-end positions from its description.
901 protected void verifyCut(SequenceI seq0, int from, int to,
902 final String msg, int seqStart)
904 List<SequenceFeature> sfs;
905 sfs = seq0.getSequenceFeatures();
907 Collections.sort(sfs, BY_DESCRIPTION);
910 * confirm the number of features has reduced by the
911 * number of features within the cut region i.e. by
912 * func(length of cut); exception is a cut at start or end of sequence,
913 * which retains the original coordinates, dataset sequence
914 * and all its features
916 boolean datasetRetained = from == 0 || to == 4;
919 // dataset and all features retained
920 assertEquals(msg, func(5), sfs.size());
922 else if (to - from == 4)
924 // all columns were cut
925 assertTrue(sfs.isEmpty());
929 // failure in checkFeatureRelocation is more informative!
930 assertEquals(msg + "wrong number of features left", func(5)
931 - func(to - from + 1), sfs.size());
935 * inspect individual features
937 for (SequenceFeature sf : sfs)
939 verifyFeatureRelocation(sf, from + 1, to + 1, !datasetRetained,
945 * Check that after Undo, every feature has start/end that match its original
946 * "start" and "end" properties
952 protected void verifyUndo(int from, int to, List<SequenceFeature> sfs)
954 for (SequenceFeature sf : sfs)
956 final int oldFrom = ((Integer) sf.getValue("from")).intValue();
957 final int oldTo = ((Integer) sf.getValue("to")).intValue();
958 String msg = String.format(
959 "Undo cut of [%d-%d], feature at [%d-%d] ", from + 1, to + 1,
961 assertEquals(msg + "start", oldFrom, sf.getBegin());
962 assertEquals(msg + "end", oldTo, sf.getEnd());
967 * Helper method to check a feature has been correctly relocated after a cut
971 * start of cut (first residue cut 1..)
973 * end of cut (last residue cut 1..)
977 private void verifyFeatureRelocation(SequenceFeature sf, int from, int to,
978 boolean newDataset, int seqStart)
980 // TODO handle the gapped sequence case as well
981 int cutSize = to - from + 1;
982 final int oldFrom = ((Integer) sf.getValue("from")).intValue();
983 final int oldTo = ((Integer) sf.getValue("to")).intValue();
984 final int oldFromPosition = oldFrom - seqStart + 1; // 1..
985 final int oldToPosition = oldTo - seqStart + 1; // 1..
987 String msg = String.format(
988 "Feature %s relocated to %d-%d after cut of %d-%d",
989 sf.getDescription(), sf.getBegin(), sf.getEnd(), from, to);
992 // dataset retained with all features unchanged
993 assertEquals("0: " + msg, oldFrom, sf.getBegin());
994 assertEquals("0: " + msg, oldTo, sf.getEnd());
996 else if (oldToPosition < from)
998 // before cut region so unchanged
999 assertEquals("1: " + msg, oldFrom, sf.getBegin());
1000 assertEquals("2: " + msg, oldTo, sf.getEnd());
1002 else if (oldFromPosition > to)
1004 // follows cut region - shift by size of cut
1005 assertEquals("3: " + msg, newDataset ? oldFrom - cutSize : oldFrom,
1007 assertEquals("4: " + msg, newDataset ? oldTo - cutSize : oldTo,
1010 else if (oldFromPosition < from && oldToPosition > to)
1012 // feature encloses cut region - shrink it right
1013 assertEquals("5: " + msg, oldFrom, sf.getBegin());
1014 assertEquals("6: " + msg, oldTo - cutSize, sf.getEnd());
1016 else if (oldFromPosition < from)
1018 // feature overlaps left side of cut region - truncated right
1019 assertEquals("7: " + msg, from - 1 + seqStart - 1, sf.getEnd());
1021 else if (oldToPosition > to)
1023 // feature overlaps right side of cut region - truncated left
1024 assertEquals("8: " + msg, newDataset ? from + seqStart - 1 : to + 1,
1026 assertEquals("9: " + msg, newDataset ? from + oldTo - to - 1 : oldTo,
1031 // feature internal to cut - should have been deleted!
1032 Assert.fail(msg + " - should have been deleted");
1037 * Test a cut action's relocation of sequence features
1039 @Test(groups = { "Functional" })
1040 public void testCut_withFeatures5prime()
1042 SequenceI seq0 = new Sequence("seq/8-11", "A-BCC");
1043 seq0.createDatasetSequence();
1044 assertEquals(8, seq0.getStart());
1045 seq0.addSequenceFeature(new SequenceFeature("", "", 10, 11, 0f,
1047 SequenceI[] seqsArray = new SequenceI[] { seq0 };
1048 AlignmentI alignment = new Alignment(seqsArray);
1051 * cut columns of A-B; same dataset sequence is retained, aligned sequence
1054 Edit ec = testee.new Edit(Action.CUT, seqsArray, 0, 3, alignment);
1055 EditCommand.cut(ec, new AlignmentI[] { alignment });
1058 * feature on CC(10-11) should still be on CC(10-11)
1060 assertSame(seq0, alignment.getSequenceAt(0));
1061 assertEquals(10, seq0.getStart());
1062 List<SequenceFeature> sfs = seq0.getSequenceFeatures();
1063 assertEquals(1, sfs.size());
1064 SequenceFeature sf = sfs.get(0);
1065 assertEquals(10, sf.getBegin());
1066 assertEquals(11, sf.getEnd());