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.List;
40 import org.testng.Assert;
41 import org.testng.annotations.BeforeClass;
42 import org.testng.annotations.BeforeMethod;
43 import org.testng.annotations.Test;
46 * Unit tests for EditCommand
51 public class EditCommandTest
54 * compute n(n+1)/2 e.g.
55 * func(5) = 5 + 4 + 3 + 2 + 1 = 15
57 private static int func(int i)
59 return i * (i + 1) / 2;
62 @BeforeClass(alwaysRun = true)
63 public void setUpJvOptionPane()
65 JvOptionPane.setInteractiveMode(false);
66 JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
69 private EditCommand testee;
71 private SequenceI[] seqs;
75 @BeforeMethod(alwaysRun = true)
78 testee = new EditCommand();
79 seqs = new SequenceI[4];
80 seqs[0] = new Sequence("seq0", "abcdefghjk");
81 seqs[0].setDatasetSequence(new Sequence("seq0ds", "abcdefghjk"));
82 seqs[1] = new Sequence("seq1", "fghjklmnopq");
83 seqs[1].setDatasetSequence(new Sequence("seq1ds", "fghjklmnopq"));
84 seqs[2] = new Sequence("seq2", "qrstuvwxyz");
85 seqs[2].setDatasetSequence(new Sequence("seq2ds", "qrstuvwxyz"));
86 seqs[3] = new Sequence("seq3", "1234567890");
87 seqs[3].setDatasetSequence(new Sequence("seq3ds", "1234567890"));
88 al = new Alignment(seqs);
89 al.setGapCharacter('?');
93 * Test inserting gap characters
95 @Test(groups = { "Functional" })
96 public void testAppendEdit_insertGap()
98 // set a non-standard gap character to prove it is actually used
99 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 3, al, true);
100 assertEquals("abcd???efghjk", seqs[0].getSequenceAsString());
101 assertEquals("fghj???klmnopq", seqs[1].getSequenceAsString());
102 assertEquals("qrst???uvwxyz", seqs[2].getSequenceAsString());
103 assertEquals("1234???567890", seqs[3].getSequenceAsString());
105 // todo: test for handling out of range positions?
109 * Test deleting characters from sequences. Note the deleteGap() action does
110 * not check that only gap characters are being removed.
112 @Test(groups = { "Functional" })
113 public void testAppendEdit_deleteGap()
115 testee.appendEdit(Action.DELETE_GAP, seqs, 4, 3, al, true);
116 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
117 assertEquals("fghjnopq", seqs[1].getSequenceAsString());
118 assertEquals("qrstxyz", seqs[2].getSequenceAsString());
119 assertEquals("1234890", seqs[3].getSequenceAsString());
123 * Test a cut action. The command should store the cut characters to support
126 @Test(groups = { "Functional" })
127 public void testCut()
129 Edit ec = testee.new Edit(Action.CUT, seqs, 4, 3, al);
130 EditCommand.cut(ec, new AlignmentI[] { al });
131 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
132 assertEquals("fghjnopq", seqs[1].getSequenceAsString());
133 assertEquals("qrstxyz", seqs[2].getSequenceAsString());
134 assertEquals("1234890", seqs[3].getSequenceAsString());
136 assertEquals("efg", new String(ec.string[0]));
137 assertEquals("klm", new String(ec.string[1]));
138 assertEquals("uvw", new String(ec.string[2]));
139 assertEquals("567", new String(ec.string[3]));
140 // TODO: case where whole sequence is deleted as nothing left; etc
144 * Test a Paste action, where this adds sequences to an alignment.
146 @Test(groups = { "Functional" }, enabled = false)
147 // TODO fix so it works
148 public void testPaste_addToAlignment()
150 SequenceI[] newSeqs = new SequenceI[2];
151 newSeqs[0] = new Sequence("newseq0", "ACEFKL");
152 newSeqs[1] = new Sequence("newseq1", "JWMPDH");
154 Edit ec = testee.new Edit(Action.PASTE, newSeqs, 0, al.getWidth(), al);
155 EditCommand.paste(ec, new AlignmentI[] { al });
156 assertEquals(6, al.getSequences().size());
157 assertEquals("1234567890", seqs[3].getSequenceAsString());
158 assertEquals("ACEFKL", seqs[4].getSequenceAsString());
159 assertEquals("JWMPDH", seqs[5].getSequenceAsString());
163 * Test insertGap followed by undo command
165 @Test(groups = { "Functional" })
166 public void testUndo_insertGap()
168 // Edit ec = testee.new Edit(Action.INSERT_GAP, seqs, 4, 3, '?');
169 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 3, al, true);
170 // check something changed
171 assertEquals("abcd???efghjk", seqs[0].getSequenceAsString());
172 testee.undoCommand(new AlignmentI[] { al });
173 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
174 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
175 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
176 assertEquals("1234567890", seqs[3].getSequenceAsString());
180 * Test deleteGap followed by undo command
182 @Test(groups = { "Functional" })
183 public void testUndo_deleteGap()
185 testee.appendEdit(Action.DELETE_GAP, seqs, 4, 3, al, true);
186 // check something changed
187 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
188 testee.undoCommand(new AlignmentI[] { al });
189 // deleteGap doesn't 'remember' deleted characters, only gaps get put back
190 assertEquals("abcd???hjk", seqs[0].getSequenceAsString());
191 assertEquals("fghj???nopq", seqs[1].getSequenceAsString());
192 assertEquals("qrst???xyz", seqs[2].getSequenceAsString());
193 assertEquals("1234???890", seqs[3].getSequenceAsString());
197 * Test several commands followed by an undo command
199 @Test(groups = { "Functional" })
200 public void testUndo_multipleCommands()
202 // delete positions 3/4/5 (counting from 1)
203 testee.appendEdit(Action.DELETE_GAP, seqs, 2, 3, al, true);
204 assertEquals("abfghjk", seqs[0].getSequenceAsString());
205 assertEquals("1267890", seqs[3].getSequenceAsString());
207 // insert 2 gaps after the second residue
208 testee.appendEdit(Action.INSERT_GAP, seqs, 2, 2, al, true);
209 assertEquals("ab??fghjk", seqs[0].getSequenceAsString());
210 assertEquals("12??67890", seqs[3].getSequenceAsString());
212 // delete positions 4/5/6
213 testee.appendEdit(Action.DELETE_GAP, seqs, 3, 3, al, true);
214 assertEquals("ab?hjk", seqs[0].getSequenceAsString());
215 assertEquals("12?890", seqs[3].getSequenceAsString());
217 // undo edit commands
218 testee.undoCommand(new AlignmentI[] { al });
219 assertEquals("ab?????hjk", seqs[0].getSequenceAsString());
220 assertEquals("12?????890", seqs[3].getSequenceAsString());
224 * Unit test for JAL-1594 bug: click and drag sequence right to insert gaps -
225 * undo did not remove them all.
227 @Test(groups = { "Functional" })
228 public void testUndo_multipleInsertGaps()
230 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 1, al, true);
231 testee.appendEdit(Action.INSERT_GAP, seqs, 5, 1, al, true);
232 testee.appendEdit(Action.INSERT_GAP, seqs, 6, 1, al, true);
234 // undo edit commands
235 testee.undoCommand(new AlignmentI[] { al });
236 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
237 assertEquals("1234567890", seqs[3].getSequenceAsString());
242 * Test cut followed by undo command
244 @Test(groups = { "Functional" })
245 public void testUndo_cut()
247 testee.appendEdit(Action.CUT, seqs, 4, 3, al, true);
248 // check something changed
249 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
250 testee.undoCommand(new AlignmentI[] { al });
251 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
252 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
253 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
254 assertEquals("1234567890", seqs[3].getSequenceAsString());
258 * Test the replace command (used to manually edit a sequence)
260 @Test(groups = { "Functional" })
261 public void testReplace()
263 // seem to need a dataset sequence on the edited sequence here
264 seqs[1].createDatasetSequence();
265 new EditCommand("", Action.REPLACE, "ZXY", new SequenceI[] { seqs[1] },
267 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
268 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
269 assertEquals("1234567890", seqs[3].getSequenceAsString());
270 seqs[1] = new Sequence("seq1", "fghjZXYnopq");
274 * Test that the addEdit command correctly merges insert gap commands when
277 @Test(groups = { "Functional" })
278 public void testAddEdit_multipleInsertGap()
281 * 3 insert gap in a row (aka mouse drag right):
283 Edit e = new EditCommand().new Edit(Action.INSERT_GAP,
284 new SequenceI[] { seqs[0] }, 1, 1, al);
286 SequenceI edited = new Sequence("seq0", "a?bcdefghjk");
287 edited.setDatasetSequence(seqs[0].getDatasetSequence());
288 e = new EditCommand().new Edit(Action.INSERT_GAP,
289 new SequenceI[] { edited }, 2, 1, al);
291 edited = new Sequence("seq0", "a??bcdefghjk");
292 edited.setDatasetSequence(seqs[0].getDatasetSequence());
293 e = new EditCommand().new Edit(Action.INSERT_GAP,
294 new SequenceI[] { edited }, 3, 1, al);
296 assertEquals(1, testee.getSize());
297 assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
298 assertEquals(1, testee.getEdit(0).getPosition());
299 assertEquals(3, testee.getEdit(0).getNumber());
302 * Add a non-contiguous edit - should not be merged.
304 e = new EditCommand().new Edit(Action.INSERT_GAP,
305 new SequenceI[] { edited }, 5, 2, al);
307 assertEquals(2, testee.getSize());
308 assertEquals(5, testee.getEdit(1).getPosition());
309 assertEquals(2, testee.getEdit(1).getNumber());
312 * Add a Delete after the Insert - should not be merged.
314 e = new EditCommand().new Edit(Action.DELETE_GAP,
315 new SequenceI[] { edited }, 6, 2, al);
317 assertEquals(3, testee.getSize());
318 assertEquals(Action.DELETE_GAP, testee.getEdit(2).getAction());
319 assertEquals(6, testee.getEdit(2).getPosition());
320 assertEquals(2, testee.getEdit(2).getNumber());
324 * Test that the addEdit command correctly merges delete gap commands when
327 @Test(groups = { "Functional" })
328 public void testAddEdit_multipleDeleteGap()
331 * 3 delete gap in a row (aka mouse drag left):
333 seqs[0].setSequence("a???bcdefghjk");
334 Edit e = new EditCommand().new Edit(Action.DELETE_GAP,
335 new SequenceI[] { seqs[0] }, 4, 1, al);
337 assertEquals(1, testee.getSize());
339 SequenceI edited = new Sequence("seq0", "a??bcdefghjk");
340 edited.setDatasetSequence(seqs[0].getDatasetSequence());
341 e = new EditCommand().new Edit(Action.DELETE_GAP,
342 new SequenceI[] { edited }, 3, 1, al);
344 assertEquals(1, testee.getSize());
346 edited = new Sequence("seq0", "a?bcdefghjk");
347 edited.setDatasetSequence(seqs[0].getDatasetSequence());
348 e = new EditCommand().new Edit(Action.DELETE_GAP,
349 new SequenceI[] { edited }, 2, 1, al);
351 assertEquals(1, testee.getSize());
352 assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
353 assertEquals(2, testee.getEdit(0).getPosition());
354 assertEquals(3, testee.getEdit(0).getNumber());
357 * Add a non-contiguous edit - should not be merged.
359 e = new EditCommand().new Edit(Action.DELETE_GAP,
360 new SequenceI[] { edited }, 2, 1, al);
362 assertEquals(2, testee.getSize());
363 assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
364 assertEquals(2, testee.getEdit(1).getPosition());
365 assertEquals(1, testee.getEdit(1).getNumber());
368 * Add an Insert after the Delete - should not be merged.
370 e = new EditCommand().new Edit(Action.INSERT_GAP,
371 new SequenceI[] { edited }, 1, 1, al);
373 assertEquals(3, testee.getSize());
374 assertEquals(Action.INSERT_GAP, testee.getEdit(2).getAction());
375 assertEquals(1, testee.getEdit(2).getPosition());
376 assertEquals(1, testee.getEdit(2).getNumber());
380 * Test that the addEdit command correctly handles 'remove gaps' edits for the
381 * case when they appear contiguous but are acting on different sequences.
382 * They should not be merged.
384 @Test(groups = { "Functional" })
385 public void testAddEdit_removeAllGaps()
387 seqs[0].setSequence("a???bcdefghjk");
388 Edit e = new EditCommand().new Edit(Action.DELETE_GAP,
389 new SequenceI[] { seqs[0] }, 4, 1, al);
392 seqs[1].setSequence("f??ghjklmnopq");
393 Edit e2 = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
394 { seqs[1] }, 3, 1, al);
396 assertEquals(2, testee.getSize());
397 assertSame(e, testee.getEdit(0));
398 assertSame(e2, testee.getEdit(1));
402 * Test that the addEdit command correctly merges insert gap commands acting
403 * on a multi-sequence selection.
405 @Test(groups = { "Functional" })
406 public void testAddEdit_groupInsertGaps()
409 * 2 insert gap in a row (aka mouse drag right), on two sequences:
411 Edit e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] {
412 seqs[0], seqs[1] }, 1, 1, al);
414 SequenceI seq1edited = new Sequence("seq0", "a?bcdefghjk");
415 seq1edited.setDatasetSequence(seqs[0].getDatasetSequence());
416 SequenceI seq2edited = new Sequence("seq1", "f?ghjklmnopq");
417 seq2edited.setDatasetSequence(seqs[1].getDatasetSequence());
418 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] {
419 seq1edited, seq2edited }, 2, 1, al);
422 assertEquals(1, testee.getSize());
423 assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
424 assertEquals(1, testee.getEdit(0).getPosition());
425 assertEquals(2, testee.getEdit(0).getNumber());
426 assertEquals(seqs[0].getDatasetSequence(), testee.getEdit(0)
427 .getSequences()[0].getDatasetSequence());
428 assertEquals(seqs[1].getDatasetSequence(), testee.getEdit(0)
429 .getSequences()[1].getDatasetSequence());
433 * Test for 'undoing' a series of gap insertions.
435 * <li>Start: ABCDEF insert 2 at pos 1</li>
436 * <li>next: A--BCDEF insert 1 at pos 4</li>
437 * <li>next: A--B-CDEF insert 2 at pos 0</li>
438 * <li>last: --A--B-CDEF</li>
441 @Test(groups = { "Functional" })
442 public void testPriorState_multipleInserts()
444 EditCommand command = new EditCommand();
445 SequenceI seq = new Sequence("", "--A--B-CDEF");
446 SequenceI ds = new Sequence("", "ABCDEF");
447 seq.setDatasetSequence(ds);
448 SequenceI[] sqs = new SequenceI[] { seq };
449 Edit e = command.new Edit(Action.INSERT_GAP, sqs, 1, 2, '-');
451 e = command.new Edit(Action.INSERT_GAP, sqs, 4, 1, '-');
453 e = command.new Edit(Action.INSERT_GAP, sqs, 0, 2, '-');
456 Map<SequenceI, SequenceI> unwound = command.priorState(false);
457 assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString());
461 * Test for 'undoing' a series of gap deletions.
463 * <li>Start: A-B-C delete 1 at pos 1</li>
464 * <li>Next: AB-C delete 1 at pos 2</li>
468 @Test(groups = { "Functional" })
469 public void testPriorState_removeAllGaps()
471 EditCommand command = new EditCommand();
472 SequenceI seq = new Sequence("", "ABC");
473 SequenceI ds = new Sequence("", "ABC");
474 seq.setDatasetSequence(ds);
475 SequenceI[] sqs = new SequenceI[] { seq };
476 Edit e = command.new Edit(Action.DELETE_GAP, sqs, 1, 1, '-');
478 e = command.new Edit(Action.DELETE_GAP, sqs, 2, 1, '-');
481 Map<SequenceI, SequenceI> unwound = command.priorState(false);
482 assertEquals("A-B-C", unwound.get(ds).getSequenceAsString());
486 * Test for 'undoing' a single delete edit.
488 @Test(groups = { "Functional" })
489 public void testPriorState_singleDelete()
491 EditCommand command = new EditCommand();
492 SequenceI seq = new Sequence("", "ABCDEF");
493 SequenceI ds = new Sequence("", "ABCDEF");
494 seq.setDatasetSequence(ds);
495 SequenceI[] sqs = new SequenceI[] { seq };
496 Edit e = command.new Edit(Action.DELETE_GAP, sqs, 2, 2, '-');
499 Map<SequenceI, SequenceI> unwound = command.priorState(false);
500 assertEquals("AB--CDEF", unwound.get(ds).getSequenceAsString());
504 * Test 'undoing' a single gap insertion edit command.
506 @Test(groups = { "Functional" })
507 public void testPriorState_singleInsert()
509 EditCommand command = new EditCommand();
510 SequenceI seq = new Sequence("", "AB---CDEF");
511 SequenceI ds = new Sequence("", "ABCDEF");
512 seq.setDatasetSequence(ds);
513 SequenceI[] sqs = new SequenceI[] { seq };
514 Edit e = command.new Edit(Action.INSERT_GAP, sqs, 2, 3, '-');
517 Map<SequenceI, SequenceI> unwound = command.priorState(false);
518 SequenceI prior = unwound.get(ds);
519 assertEquals("ABCDEF", prior.getSequenceAsString());
520 assertEquals(1, prior.getStart());
521 assertEquals(6, prior.getEnd());
525 * Test 'undoing' a single gap insertion edit command, on a sequence whose
526 * start residue is other than 1
528 @Test(groups = { "Functional" })
529 public void testPriorState_singleInsertWithOffset()
531 EditCommand command = new EditCommand();
532 SequenceI seq = new Sequence("", "AB---CDEF", 8, 13);
533 // SequenceI ds = new Sequence("", "ABCDEF", 8, 13);
534 // seq.setDatasetSequence(ds);
535 seq.createDatasetSequence();
536 SequenceI[] sqs = new SequenceI[] { seq };
537 Edit e = command.new Edit(Action.INSERT_GAP, sqs, 2, 3, '-');
540 Map<SequenceI, SequenceI> unwound = command.priorState(false);
541 SequenceI prior = unwound.get(seq.getDatasetSequence());
542 assertEquals("ABCDEF", prior.getSequenceAsString());
543 assertEquals(8, prior.getStart());
544 assertEquals(13, prior.getEnd());
548 * Test that mimics 'remove all gaps' action. This generates delete gap edits
549 * for contiguous gaps in each sequence separately.
551 @Test(groups = { "Functional" })
552 public void testPriorState_removeGapsMultipleSeqs()
554 EditCommand command = new EditCommand();
555 String original1 = "--ABC-DEF";
556 String original2 = "FG-HI--J";
557 String original3 = "M-NOPQ";
560 * Two edits for the first sequence
562 SequenceI seq = new Sequence("", "ABC-DEF");
563 SequenceI ds1 = new Sequence("", "ABCDEF");
564 seq.setDatasetSequence(ds1);
565 SequenceI[] sqs = new SequenceI[] { seq };
566 Edit e = command.new Edit(Action.DELETE_GAP, sqs, 0, 2, '-');
568 seq = new Sequence("", "ABCDEF");
569 seq.setDatasetSequence(ds1);
570 sqs = new SequenceI[] { seq };
571 e = command.new Edit(Action.DELETE_GAP, sqs, 3, 1, '-');
575 * Two edits for the second sequence
577 seq = new Sequence("", "FGHI--J");
578 SequenceI ds2 = new Sequence("", "FGHIJ");
579 seq.setDatasetSequence(ds2);
580 sqs = new SequenceI[] { seq };
581 e = command.new Edit(Action.DELETE_GAP, sqs, 2, 1, '-');
583 seq = new Sequence("", "FGHIJ");
584 seq.setDatasetSequence(ds2);
585 sqs = new SequenceI[] { seq };
586 e = command.new Edit(Action.DELETE_GAP, sqs, 4, 2, '-');
590 * One edit for the third sequence.
592 seq = new Sequence("", "MNOPQ");
593 SequenceI ds3 = new Sequence("", "MNOPQ");
594 seq.setDatasetSequence(ds3);
595 sqs = new SequenceI[] { seq };
596 e = command.new Edit(Action.DELETE_GAP, sqs, 1, 1, '-');
599 Map<SequenceI, SequenceI> unwound = command.priorState(false);
600 assertEquals(original1, unwound.get(ds1).getSequenceAsString());
601 assertEquals(original2, unwound.get(ds2).getSequenceAsString());
602 assertEquals(original3, unwound.get(ds3).getSequenceAsString());
606 * Test that mimics 'remove all gapped columns' action. This generates a
607 * series Delete Gap edits that each act on all sequences that share a gapped
610 @Test(groups = { "Functional" })
611 public void testPriorState_removeGappedCols()
613 EditCommand command = new EditCommand();
614 String original1 = "--ABC--DEF";
615 String original2 = "-G-HI--J";
616 String original3 = "-M-NO--PQ";
619 * First edit deletes the first column.
621 SequenceI seq1 = new Sequence("", "-ABC--DEF");
622 SequenceI ds1 = new Sequence("", "ABCDEF");
623 seq1.setDatasetSequence(ds1);
624 SequenceI seq2 = new Sequence("", "G-HI--J");
625 SequenceI ds2 = new Sequence("", "GHIJ");
626 seq2.setDatasetSequence(ds2);
627 SequenceI seq3 = new Sequence("", "M-NO--PQ");
628 SequenceI ds3 = new Sequence("", "MNOPQ");
629 seq3.setDatasetSequence(ds3);
630 SequenceI[] sqs = new SequenceI[] { seq1, seq2, seq3 };
631 Edit e = command.new Edit(Action.DELETE_GAP, sqs, 0, 1, '-');
635 * Second edit deletes what is now columns 4 and 5.
637 seq1 = new Sequence("", "-ABCDEF");
638 seq1.setDatasetSequence(ds1);
639 seq2 = new Sequence("", "G-HIJ");
640 seq2.setDatasetSequence(ds2);
641 seq3 = new Sequence("", "M-NOPQ");
642 seq3.setDatasetSequence(ds3);
643 sqs = new SequenceI[] { seq1, seq2, seq3 };
644 e = command.new Edit(Action.DELETE_GAP, sqs, 4, 2, '-');
647 Map<SequenceI, SequenceI> unwound = command.priorState(false);
648 assertEquals(original1, unwound.get(ds1).getSequenceAsString());
649 assertEquals(original2, unwound.get(ds2).getSequenceAsString());
650 assertEquals(original3, unwound.get(ds3).getSequenceAsString());
651 assertEquals(ds1, unwound.get(ds1).getDatasetSequence());
652 assertEquals(ds2, unwound.get(ds2).getDatasetSequence());
653 assertEquals(ds3, unwound.get(ds3).getDatasetSequence());
657 * Test a cut action's relocation of sequence features
659 @Test(groups = { "Functional" })
660 public void testCut_withFeatures()
663 * create sequence features before, after and overlapping
664 * a cut of columns/residues 4-7
666 SequenceI seq0 = seqs[0]; // abcdefghjk/1-10
667 seq0.addSequenceFeature(new SequenceFeature("before", "", 1, 3, 0f,
669 seq0.addSequenceFeature(new SequenceFeature("overlap left", "", 2, 6,
671 seq0.addSequenceFeature(new SequenceFeature("internal", "", 5, 6, 0f,
673 seq0.addSequenceFeature(new SequenceFeature("overlap right", "", 7, 8,
675 seq0.addSequenceFeature(new SequenceFeature("after", "", 8, 10, 0f,
679 * add some contact features
681 SequenceFeature internalContact = new SequenceFeature("disulphide bond", "", 5,
683 seq0.addSequenceFeature(internalContact); // should get deleted
684 SequenceFeature overlapLeftContact = new SequenceFeature(
685 "disulphide bond", "", 2, 6, 0f, null);
686 seq0.addSequenceFeature(overlapLeftContact); // should get deleted
687 SequenceFeature overlapRightContact = new SequenceFeature(
688 "disulphide bond", "", 5, 8, 0f, null);
689 seq0.addSequenceFeature(overlapRightContact); // should get deleted
690 SequenceFeature spanningContact = new SequenceFeature(
691 "disulphide bond", "", 2, 9, 0f, null);
692 seq0.addSequenceFeature(spanningContact); // should get shortened 3'
695 * cut columns 3-6 (base 0), residues d-g 4-7
697 Edit ec = testee.new Edit(Action.CUT, seqs, 3, 4, al); // cols 3-6 base 0
698 EditCommand.cut(ec, new AlignmentI[] { al });
700 List<SequenceFeature> sfs = seq0.getSequenceFeatures();
701 SequenceFeatures.sortFeatures(sfs, true);
703 assertEquals(5, sfs.size()); // features internal to cut were deleted
704 SequenceFeature sf = sfs.get(0);
705 assertEquals("before", sf.getType());
706 assertEquals(1, sf.getBegin());
707 assertEquals(3, sf.getEnd());
709 assertEquals("disulphide bond", sf.getType());
710 assertEquals(2, sf.getBegin());
711 assertEquals(5, sf.getEnd()); // truncated by cut
713 assertEquals("overlap left", sf.getType());
714 assertEquals(2, sf.getBegin());
715 assertEquals(3, sf.getEnd()); // truncated by cut
717 assertEquals("after", sf.getType());
718 assertEquals(4, sf.getBegin()); // shifted left by cut
719 assertEquals(6, sf.getEnd()); // shifted left by cut
721 assertEquals("overlap right", sf.getType());
722 assertEquals(4, sf.getBegin()); // shifted left by cut
723 assertEquals(4, sf.getEnd()); // truncated by cut
727 * Test a cut action's relocation of sequence features, with full coverage of
728 * all possible feature and cut locations for a 5-position ungapped sequence
730 @Test(groups = { "Functional" })
731 public void testCut_withFeatures_exhaustive()
734 * create a sequence features on each subrange of 1-5
736 SequenceI seq0 = new Sequence("seq", "ABCDE");
737 AlignmentI alignment = new Alignment(new SequenceI[] { seq0 });
738 alignment.setDataset(null);
739 for (int from = 1; from <= seq0.getLength(); from++)
741 for (int to = from; to <= seq0.getLength(); to++)
743 String desc = String.format("%d-%d", from, to);
744 SequenceFeature sf = new SequenceFeature("test", desc, from, to,
746 sf.setValue("from", Integer.valueOf(from));
747 sf.setValue("to", Integer.valueOf(to));
748 seq0.addSequenceFeature(sf);
752 List<SequenceFeature> sfs = seq0.getSequenceFeatures();
753 assertEquals(func(5), sfs.size());
756 * now perform all possible cuts of subranges of 1-5 (followed by Undo)
757 * and validate the resulting remaining sequence features!
759 SequenceI[] sqs = new SequenceI[] { seq0 };
761 for (int from = 0; from < seq0.getLength(); from++)
763 for (int to = from; to < seq0.getLength(); to++)
765 testee.appendEdit(Action.CUT, sqs, from, (to - from + 1),
768 sfs = seq0.getSequenceFeatures();
771 * confirm the number of features has reduced by the
772 * number of features within the cut region i.e. by
773 * func(length of cut)
775 String msg = String.format("Cut %d-%d ", from, to);
778 // all columns were cut
779 assertTrue(sfs.isEmpty());
783 assertEquals(msg + "wrong number of features left", func(5)
784 - func(to - from + 1), sfs.size());
788 * inspect individual features
790 for (SequenceFeature sf : sfs)
792 checkFeatureRelocation(sf, from + 1, to + 1, from > 0);
796 * undo ready for next cut
798 testee.undoCommand(new AlignmentI[] { alignment });
799 int size = seq0.getSequenceFeatures().size();
800 assertEquals(func(5), size);
806 * Helper method to check a feature has been correctly relocated after a cut
810 * start of cut (first residue cut)
812 * end of cut (last residue cut)
815 private void checkFeatureRelocation(SequenceFeature sf, int from, int to,
818 // TODO handle the gapped sequence case as well
819 int cutSize = to - from + 1;
820 final int oldFrom = ((Integer) sf.getValue("from")).intValue();
821 final int oldTo = ((Integer) sf.getValue("to")).intValue();
823 String msg = String.format(
824 "Feature %s relocated to %d-%d after cut of %d-%d",
825 sf.getDescription(), sf.getBegin(), sf.getEnd(), from, to);
828 // before cut region so unchanged
829 assertEquals("1: " + msg, oldFrom, sf.getBegin());
830 assertEquals("2: " + msg, oldTo, sf.getEnd());
832 else if (oldFrom > to)
834 // follows cut region - shift by size of cut
835 assertEquals("3: " + msg, newDataset ? oldFrom - cutSize : oldFrom,
837 assertEquals("4: " + msg, newDataset ? oldTo - cutSize : oldTo,
840 else if (oldFrom < from && oldTo > to)
842 // feature encloses cut region - shrink it right
843 assertEquals("5: " + msg, oldFrom, sf.getBegin());
844 assertEquals("6: " + msg, oldTo - cutSize, sf.getEnd());
846 else if (oldFrom < from)
848 // feature overlaps left side of cut region - truncated right
849 assertEquals("7: " + msg, from - 1, sf.getEnd());
853 // feature overlaps right side of cut region - truncated left
854 assertEquals("8: " + msg, newDataset ? from : to + 1, sf.getBegin());
855 assertEquals("9: " + msg, newDataset ? from + oldTo - to - 1 : oldTo,
860 // feature internal to cut - should have been deleted!
861 Assert.fail(msg + " - should have been deleted");
866 * Test a cut action's relocation of sequence features
868 @Test(groups = { "Functional" })
869 public void testCut_withFeatures5prime()
871 SequenceI seq0 = new Sequence("seq/8-11", "A-BCC");
872 seq0.createDatasetSequence();
873 assertEquals(8, seq0.getStart());
874 seq0.addSequenceFeature(new SequenceFeature("", "", 10, 11, 0f,
876 SequenceI[] seqsArray = new SequenceI[] { seq0 };
877 AlignmentI alignment = new Alignment(seqsArray);
880 * cut columns of A-B; same dataset sequence is retained, aligned sequence
883 Edit ec = testee.new Edit(Action.CUT, seqsArray, 0, 3, alignment);
884 EditCommand.cut(ec, new AlignmentI[] { alignment });
887 * feature on CC(10-11) should still be on CC(10-11)
889 assertSame(seq0, alignment.getSequenceAt(0));
890 assertEquals(10, seq0.getStart());
891 List<SequenceFeature> sfs = seq0.getSequenceFeatures();
892 assertEquals(1, sfs.size());
893 SequenceFeature sf = sfs.get(0);
894 assertEquals(10, sf.getBegin());
895 assertEquals(11, sf.getEnd());
897 // TODO add further cases including Undo - see JAL-2541