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.assertNull;
25 import static org.testng.AssertJUnit.assertSame;
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];
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,
678 Edit ec = testee.new Edit(Action.CUT, seqs, 3, 4, al); // cols 3-6 base 0
679 EditCommand.cut(ec, new AlignmentI[] { al });
681 List<SequenceFeature> sfs = seq0.getSequenceFeatures();
682 SequenceFeatures.sortFeatures(sfs, true);
684 assertEquals(4, sfs.size()); // feature internal to cut has been deleted
685 SequenceFeature sf = sfs.get(0);
686 assertEquals("before", sf.getType());
687 assertEquals(1, sf.getBegin());
688 assertEquals(3, sf.getEnd());
690 assertEquals("overlap left", sf.getType());
691 assertEquals(2, sf.getBegin());
692 assertEquals(3, sf.getEnd()); // truncated by cut
694 assertEquals("overlap right", sf.getType());
695 assertEquals(4, sf.getBegin()); // shifted left by cut
696 assertEquals(5, sf.getEnd()); // truncated by cut
698 assertEquals("after", sf.getType());
699 assertEquals(4, sf.getBegin()); // shifted left by cut
700 assertEquals(6, sf.getEnd()); // shifted left by cut
704 * Test a cut action's relocation of sequence features, with full coverage of
705 * all possible feature and cut locations for a 5-position ungapped sequence
707 @Test(groups = { "Functional" })
708 public void testCut_withFeatures_exhaustive()
711 * create a sequence features on each subrange of 1-5
713 SequenceI seq0 = new Sequence("seq", "ABCDE");
714 AlignmentI alignment = new Alignment(new SequenceI[] { seq0 });
715 alignment.setDataset(null);
716 for (int from = 1; from <= seq0.getLength(); from++)
718 for (int to = from; to <= seq0.getLength(); to++)
720 String desc = String.format("%d-%d", from, to);
721 SequenceFeature sf = new SequenceFeature("test", desc, from, to,
724 sf.setValue("from", Integer.valueOf(from));
725 sf.setValue("to", Integer.valueOf(to));
726 seq0.addSequenceFeature(sf);
730 List<SequenceFeature> sfs = seq0.getSequenceFeatures();
731 assertEquals(func(5), sfs.size());
734 * now perform all possible cuts of subranges of 1-5 (followed by Undo)
735 * and validate the resulting remaining sequence features!
737 SequenceI[] sqs = new SequenceI[] { seq0 };
739 // goal is to have this passing for all from/to values!!
740 // for (int from = 0; from < seq0.getLength(); from++)
742 // for (int to = from; to < seq0.getLength(); to++)
743 for (int from = 1; from < 3; from++)
745 for (int to = 2; to < 3; to++)
747 testee.appendEdit(Action.CUT, sqs, from, (to - from + 1),
750 sfs = seq0.getSequenceFeatures();
753 * confirm the number of features has reduced by the
754 * number of features within the cut region i.e. by
755 * func(length of cut)
757 String msg = String.format("Cut %d-%d ", from, to);
765 assertEquals(msg + "wrong number of features left", func(5)
766 - func(to - from + 1), sfs.size());
770 * inspect individual features
774 for (SequenceFeature sf : sfs)
776 checkFeatureRelocation(sf, from + 1, to + 1);
780 * undo ready for next cut
782 testee.undoCommand(new AlignmentI[] { alignment });
783 assertEquals(func(5), seq0.getSequenceFeatures().size());
789 * Helper method to check a feature has been correctly relocated after a cut
793 * start of cut (first residue cut)
795 * end of cut (last residue cut)
797 private void checkFeatureRelocation(SequenceFeature sf, int from, int to)
799 // TODO handle the gapped sequence case as well
800 int cutSize = to - from + 1;
801 int oldFrom = ((Integer) sf.getValue("from")).intValue();
802 int oldTo = ((Integer) sf.getValue("to")).intValue();
804 String msg = String.format(
805 "Feature %s relocated to %d-%d after cut of %d-%d",
806 sf.getDescription(), sf.getBegin(), sf.getEnd(), from, to);
809 // before cut region so unchanged
810 assertEquals("1: " + msg, oldFrom, sf.getBegin());
811 assertEquals("2: " + msg, oldTo, sf.getEnd());
813 else if (oldFrom > to)
815 // follows cut region - shift by size of cut
816 assertEquals("3: " + msg, oldFrom - cutSize, sf.getBegin());
817 assertEquals("4: " + msg, oldTo - cutSize, sf.getEnd());
819 else if (oldFrom < from && oldTo > to)
821 // feature encloses cut region - shrink it right
822 assertEquals("5: " + msg, oldFrom, sf.getBegin());
823 assertEquals("6: " + msg, oldTo - cutSize, sf.getEnd());
825 else if (oldFrom < from)
827 // feature overlaps left side of cut region - truncated right
828 assertEquals("7: " + msg, from - 1, sf.getEnd());
832 // feature overlaps right side of cut region - truncated left
833 assertEquals("8: " + msg, from, sf.getBegin());
834 assertEquals("9: " + msg, from + oldTo - to - 1, sf.getEnd());
838 // feature internal to cut - should have been deleted!
839 Assert.fail(msg + " - should have been deleted");
844 * Test a cut action's relocation of sequence features
846 @Test(groups = { "Functional" })
847 public void testCut_gappedWithFeatures()
850 * create sequence features before, after and overlapping
851 * a cut of columns/residues 4-7
853 SequenceI seq0 = new Sequence("seq", "A-BCC");
854 seq0.addSequenceFeature(new SequenceFeature("", "", 3, 4, 0f,
856 AlignmentI alignment = new Alignment(new SequenceI[] { seq0 });
857 // cut columns of A-B
858 Edit ec = testee.new Edit(Action.CUT, seqs, 0, 3, alignment); // cols 0-3
860 EditCommand.cut(ec, new AlignmentI[] { alignment });
863 * feature on CC(3-4) should now be on CC(1-2)
865 List<SequenceFeature> sfs = seq0.getSequenceFeatures();
866 assertEquals(1, sfs.size());
867 SequenceFeature sf = sfs.get(0);
868 assertEquals(1, sf.getBegin());
869 assertEquals(2, sf.getEnd());
871 // TODO add further cases including Undo - see JAL-2541