1 package jalview.commands;
3 import static org.testng.AssertJUnit.assertEquals;
4 import static org.testng.AssertJUnit.assertSame;
6 import jalview.commands.EditCommand.Action;
7 import jalview.commands.EditCommand.Edit;
8 import jalview.datamodel.Alignment;
9 import jalview.datamodel.AlignmentI;
10 import jalview.datamodel.Sequence;
11 import jalview.datamodel.SequenceI;
15 import org.testng.annotations.BeforeMethod;
16 import org.testng.annotations.Test;
19 * Unit tests for EditCommand
24 public class EditCommandTest
27 private EditCommand testee;
29 private SequenceI[] seqs;
33 @BeforeMethod(groups =
37 testee = new EditCommand();
38 seqs = new SequenceI[4];
39 seqs[0] = new Sequence("seq0", "abcdefghjk");
40 seqs[0].setDatasetSequence(new Sequence("seq0ds", "abcdefghjk"));
41 seqs[1] = new Sequence("seq1", "fghjklmnopq");
42 seqs[1].setDatasetSequence(new Sequence("seq1ds", "fghjklmnopq"));
43 seqs[2] = new Sequence("seq2", "qrstuvwxyz");
44 seqs[2].setDatasetSequence(new Sequence("seq2ds", "qrstuvwxyz"));
45 seqs[3] = new Sequence("seq3", "1234567890");
46 seqs[3].setDatasetSequence(new Sequence("seq3ds", "1234567890"));
47 al = new Alignment(seqs);
48 al.setGapCharacter('?');
52 * Test inserting gap characters
54 @Test(groups ={ "Functional" })
55 public void testAppendEdit_insertGap()
57 // set a non-standard gap character to prove it is actually used
58 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 3, al, true);
59 assertEquals("abcd???efghjk", seqs[0].getSequenceAsString());
60 assertEquals("fghj???klmnopq", seqs[1].getSequenceAsString());
61 assertEquals("qrst???uvwxyz", seqs[2].getSequenceAsString());
62 assertEquals("1234???567890", seqs[3].getSequenceAsString());
64 // todo: test for handling out of range positions?
68 * Test deleting characters from sequences. Note the deleteGap() action does
69 * not check that only gap characters are being removed.
71 @Test(groups ={ "Functional" })
72 public void testAppendEdit_deleteGap()
74 testee.appendEdit(Action.DELETE_GAP, seqs, 4, 3, al, true);
75 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
76 assertEquals("fghjnopq", seqs[1].getSequenceAsString());
77 assertEquals("qrstxyz", seqs[2].getSequenceAsString());
78 assertEquals("1234890", seqs[3].getSequenceAsString());
82 * Test a cut action. The command should store the cut characters to support
85 @Test(groups ={ "Functional" })
88 Edit ec = testee.new Edit(Action.CUT, seqs, 4, 3, al);
89 testee.cut(ec, new AlignmentI[]
91 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
92 assertEquals("fghjnopq", seqs[1].getSequenceAsString());
93 assertEquals("qrstxyz", seqs[2].getSequenceAsString());
94 assertEquals("1234890", seqs[3].getSequenceAsString());
96 assertEquals("efg", new String(ec.string[0]));
97 assertEquals("klm", new String(ec.string[1]));
98 assertEquals("uvw", new String(ec.string[2]));
99 assertEquals("567", new String(ec.string[3]));
100 // TODO: case where whole sequence is deleted as nothing left; etc
104 * Test a Paste action, where this adds sequences to an alignment.
107 { "Functional" }, enabled = false)
108 // TODO fix so it works
109 public void testPaste_addToAlignment()
111 SequenceI[] newSeqs = new SequenceI[2];
112 newSeqs[0] = new Sequence("newseq0", "ACEFKL");
113 newSeqs[1] = new Sequence("newseq1", "JWMPDH");
115 Edit ec = testee.new Edit(Action.PASTE, newSeqs, 0, al.getWidth(), al);
116 testee.paste(ec, new AlignmentI[]
118 assertEquals(6, al.getSequences().size());
119 assertEquals("1234567890", seqs[3].getSequenceAsString());
120 assertEquals("ACEFKL", seqs[4].getSequenceAsString());
121 assertEquals("JWMPDH", seqs[5].getSequenceAsString());
125 * Test insertGap followed by undo command
127 @Test(groups ={ "Functional" })
128 public void testUndo_insertGap()
130 // Edit ec = testee.new Edit(Action.INSERT_GAP, seqs, 4, 3, '?');
131 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 3, al, true);
132 // check something changed
133 assertEquals("abcd???efghjk", seqs[0].getSequenceAsString());
134 testee.undoCommand(new AlignmentI[]
136 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
137 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
138 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
139 assertEquals("1234567890", seqs[3].getSequenceAsString());
143 * Test deleteGap followed by undo command
145 @Test(groups ={ "Functional" })
146 public void testUndo_deleteGap()
148 testee.appendEdit(Action.DELETE_GAP, seqs, 4, 3, al, true);
149 // check something changed
150 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
151 testee.undoCommand(new AlignmentI[]
153 // deleteGap doesn't 'remember' deleted characters, only gaps get put back
154 assertEquals("abcd???hjk", seqs[0].getSequenceAsString());
155 assertEquals("fghj???nopq", seqs[1].getSequenceAsString());
156 assertEquals("qrst???xyz", seqs[2].getSequenceAsString());
157 assertEquals("1234???890", seqs[3].getSequenceAsString());
161 * Test several commands followed by an undo command
163 @Test(groups ={ "Functional" })
164 public void testUndo_multipleCommands()
166 // delete positions 3/4/5 (counting from 1)
167 testee.appendEdit(Action.DELETE_GAP, seqs, 2, 3, al, true);
168 assertEquals("abfghjk", seqs[0].getSequenceAsString());
169 assertEquals("1267890", seqs[3].getSequenceAsString());
171 // insert 2 gaps after the second residue
172 testee.appendEdit(Action.INSERT_GAP, seqs, 2, 2, al, true);
173 assertEquals("ab??fghjk", seqs[0].getSequenceAsString());
174 assertEquals("12??67890", seqs[3].getSequenceAsString());
176 // delete positions 4/5/6
177 testee.appendEdit(Action.DELETE_GAP, seqs, 3, 3, al, true);
178 assertEquals("ab?hjk", seqs[0].getSequenceAsString());
179 assertEquals("12?890", seqs[3].getSequenceAsString());
181 // undo edit commands
182 testee.undoCommand(new AlignmentI[]
184 assertEquals("ab?????hjk", seqs[0].getSequenceAsString());
185 assertEquals("12?????890", seqs[3].getSequenceAsString());
189 * Unit test for JAL-1594 bug: click and drag sequence right to insert gaps -
190 * undo did not remove them all.
192 @Test(groups ={ "Functional" })
193 public void testUndo_multipleInsertGaps()
195 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 1, al, true);
196 testee.appendEdit(Action.INSERT_GAP, seqs, 5, 1, al, true);
197 testee.appendEdit(Action.INSERT_GAP, seqs, 6, 1, al, true);
199 // undo edit commands
200 testee.undoCommand(new AlignmentI[]
202 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
203 assertEquals("1234567890", seqs[3].getSequenceAsString());
208 * Test cut followed by undo command
210 @Test(groups ={ "Functional" })
211 public void testUndo_cut()
213 testee.appendEdit(Action.CUT, seqs, 4, 3, al, true);
214 // check something changed
215 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
216 testee.undoCommand(new AlignmentI[]
218 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
219 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
220 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
221 assertEquals("1234567890", seqs[3].getSequenceAsString());
225 * Test the replace command (used to manually edit a sequence)
227 @Test(groups ={ "Functional" })
228 public void testReplace()
230 // seem to need a dataset sequence on the edited sequence here
231 seqs[1].setDatasetSequence(seqs[1]);
232 new EditCommand("", Action.REPLACE, "ZXY", new SequenceI[]
233 { seqs[1] }, 4, 8, al);
234 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
235 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
236 assertEquals("1234567890", seqs[3].getSequenceAsString());
237 seqs[1] = new Sequence("seq1", "fghjZXYnopq");
241 * Test that the addEdit command correctly merges insert gap commands when
244 @Test(groups ={ "Functional" })
245 public void testAddEdit_multipleInsertGap()
248 * 3 insert gap in a row (aka mouse drag right):
250 Edit e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
251 { seqs[0] }, 1, 1, al);
253 SequenceI edited = new Sequence("seq0", "a?bcdefghjk");
254 edited.setDatasetSequence(seqs[0].getDatasetSequence());
255 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
256 { edited }, 2, 1, al);
258 edited = new Sequence("seq0", "a??bcdefghjk");
259 edited.setDatasetSequence(seqs[0].getDatasetSequence());
260 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
261 { edited }, 3, 1, al);
263 assertEquals(1, testee.getSize());
264 assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
265 assertEquals(1, testee.getEdit(0).getPosition());
266 assertEquals(3, testee.getEdit(0).getNumber());
269 * Add a non-contiguous edit - should not be merged.
271 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
272 { edited }, 5, 2, al);
274 assertEquals(2, testee.getSize());
275 assertEquals(5, testee.getEdit(1).getPosition());
276 assertEquals(2, testee.getEdit(1).getNumber());
279 * Add a Delete after the Insert - should not be merged.
281 e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
282 { edited }, 6, 2, al);
284 assertEquals(3, testee.getSize());
285 assertEquals(Action.DELETE_GAP, testee.getEdit(2).getAction());
286 assertEquals(6, testee.getEdit(2).getPosition());
287 assertEquals(2, testee.getEdit(2).getNumber());
291 * Test that the addEdit command correctly merges delete gap commands when
294 @Test(groups ={ "Functional" })
295 public void testAddEdit_multipleDeleteGap()
298 * 3 delete gap in a row (aka mouse drag left):
300 seqs[0].setSequence("a???bcdefghjk");
301 Edit e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
302 { seqs[0] }, 4, 1, al);
304 assertEquals(1, testee.getSize());
306 SequenceI edited = new Sequence("seq0", "a??bcdefghjk");
307 edited.setDatasetSequence(seqs[0].getDatasetSequence());
308 e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
309 { edited }, 3, 1, al);
311 assertEquals(1, testee.getSize());
313 edited = new Sequence("seq0", "a?bcdefghjk");
314 edited.setDatasetSequence(seqs[0].getDatasetSequence());
315 e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
316 { edited }, 2, 1, al);
318 assertEquals(1, testee.getSize());
319 assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
320 assertEquals(2, testee.getEdit(0).getPosition());
321 assertEquals(3, testee.getEdit(0).getNumber());
324 * Add a non-contiguous edit - should not be merged.
326 e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
327 { edited }, 2, 1, al);
329 assertEquals(2, testee.getSize());
330 assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
331 assertEquals(2, testee.getEdit(1).getPosition());
332 assertEquals(1, testee.getEdit(1).getNumber());
335 * Add an Insert after the Delete - should not be merged.
337 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
338 { edited }, 1, 1, al);
340 assertEquals(3, testee.getSize());
341 assertEquals(Action.INSERT_GAP, testee.getEdit(2).getAction());
342 assertEquals(1, testee.getEdit(2).getPosition());
343 assertEquals(1, testee.getEdit(2).getNumber());
347 * Test that the addEdit command correctly handles 'remove gaps' edits for the
348 * case when they appear contiguous but are acting on different sequences.
349 * They should not be merged.
351 @Test(groups ={ "Functional" })
352 public void testAddEdit_removeAllGaps()
354 seqs[0].setSequence("a???bcdefghjk");
355 Edit e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
356 { seqs[0] }, 4, 1, al);
359 seqs[1].setSequence("f??ghjklmnopq");
360 Edit e2 = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
361 { seqs[1] }, 3, 1, al);
363 assertEquals(2, testee.getSize());
364 assertSame(e, testee.getEdit(0));
365 assertSame(e2, testee.getEdit(1));
369 * Test that the addEdit command correctly merges insert gap commands acting
370 * on a multi-sequence selection.
372 @Test(groups ={ "Functional" })
373 public void testAddEdit_groupInsertGaps()
376 * 2 insert gap in a row (aka mouse drag right), on two sequences:
378 Edit e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
379 { seqs[0], seqs[1] }, 1, 1, al);
381 SequenceI seq1edited = new Sequence("seq0", "a?bcdefghjk");
382 seq1edited.setDatasetSequence(seqs[0].getDatasetSequence());
383 SequenceI seq2edited = new Sequence("seq1", "f?ghjklmnopq");
384 seq2edited.setDatasetSequence(seqs[1].getDatasetSequence());
385 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
386 { seq1edited, seq2edited }, 2, 1, al);
389 assertEquals(1, testee.getSize());
390 assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
391 assertEquals(1, testee.getEdit(0).getPosition());
392 assertEquals(2, testee.getEdit(0).getNumber());
393 assertEquals(seqs[0].getDatasetSequence(), testee.getEdit(0)
394 .getSequences()[0].getDatasetSequence());
395 assertEquals(seqs[1].getDatasetSequence(), testee.getEdit(0)
396 .getSequences()[1].getDatasetSequence());
400 * Test for 'undoing' a series of gap insertions.
402 * <li>Start: ABCDEF insert 2 at pos 1</li>
403 * <li>next: A--BCDEF insert 1 at pos 4</li>
404 * <li>next: A--B-CDEF insert 2 at pos 0</li>
405 * <li>last: --A--B-CDEF</li>
408 @Test(groups ={ "Functional" })
409 public void testPriorState_multipleInserts()
411 EditCommand command = new EditCommand();
412 SequenceI seq = new Sequence("", "--A--B-CDEF");
413 SequenceI ds = new Sequence("", "ABCDEF");
414 seq.setDatasetSequence(ds);
415 SequenceI[] seqs = new SequenceI[]
417 Edit e = command.new Edit(Action.INSERT_GAP, seqs, 1, 2, '-');
419 e = command.new Edit(Action.INSERT_GAP, seqs, 4, 1, '-');
421 e = command.new Edit(Action.INSERT_GAP, seqs, 0, 2, '-');
424 Map<SequenceI, SequenceI> unwound = command.priorState(false);
425 assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString());
429 * Test for 'undoing' a series of gap deletions.
431 * <li>Start: A-B-C delete 1 at pos 1</li>
432 * <li>Next: AB-C delete 1 at pos 2</li>
436 @Test(groups ={ "Functional" })
437 public void testPriorState_removeAllGaps()
439 EditCommand command = new EditCommand();
440 SequenceI seq = new Sequence("", "ABC");
441 SequenceI ds = new Sequence("", "ABC");
442 seq.setDatasetSequence(ds);
443 SequenceI[] seqs = new SequenceI[]
445 Edit e = command.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
447 e = command.new Edit(Action.DELETE_GAP, seqs, 2, 1, '-');
450 Map<SequenceI, SequenceI> unwound = command.priorState(false);
451 assertEquals("A-B-C", unwound.get(ds).getSequenceAsString());
455 * Test for 'undoing' a single delete edit.
457 @Test(groups ={ "Functional" })
458 public void testPriorState_singleDelete()
460 EditCommand command = new EditCommand();
461 SequenceI seq = new Sequence("", "ABCDEF");
462 SequenceI ds = new Sequence("", "ABCDEF");
463 seq.setDatasetSequence(ds);
464 SequenceI[] seqs = new SequenceI[]
466 Edit e = command.new Edit(Action.DELETE_GAP, seqs, 2, 2, '-');
469 Map<SequenceI, SequenceI> unwound = command.priorState(false);
470 assertEquals("AB--CDEF", unwound.get(ds).getSequenceAsString());
474 * Test 'undoing' a single gap insertion edit command.
476 @Test(groups ={ "Functional" })
477 public void testPriorState_singleInsert()
479 EditCommand command = new EditCommand();
480 SequenceI seq = new Sequence("", "AB---CDEF");
481 SequenceI ds = new Sequence("", "ABCDEF");
482 seq.setDatasetSequence(ds);
483 SequenceI[] seqs = new SequenceI[]
485 Edit e = command.new Edit(Action.INSERT_GAP, seqs, 2, 3, '-');
488 Map<SequenceI, SequenceI> unwound = command.priorState(false);
489 assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString());
493 * Test that mimics 'remove all gaps' action. This generates delete gap edits
494 * for contiguous gaps in each sequence separately.
496 @Test(groups ={ "Functional" })
497 public void testPriorState_removeGapsMultipleSeqs()
499 EditCommand command = new EditCommand();
500 String original1 = "--ABC-DEF";
501 String original2 = "FG-HI--J";
502 String original3 = "M-NOPQ";
505 * Two edits for the first sequence
507 SequenceI seq = new Sequence("", "ABC-DEF");
508 SequenceI ds1 = new Sequence("", "ABCDEF");
509 seq.setDatasetSequence(ds1);
510 SequenceI[] seqs = new SequenceI[]
512 Edit e = command.new Edit(Action.DELETE_GAP, seqs, 0, 2, '-');
514 seq = new Sequence("", "ABCDEF");
515 seq.setDatasetSequence(ds1);
516 seqs = new SequenceI[]
518 e = command.new Edit(Action.DELETE_GAP, seqs, 3, 1, '-');
522 * Two edits for the second sequence
524 seq = new Sequence("", "FGHI--J");
525 SequenceI ds2 = new Sequence("", "FGHIJ");
526 seq.setDatasetSequence(ds2);
527 seqs = new SequenceI[]
529 e = command.new Edit(Action.DELETE_GAP, seqs, 2, 1, '-');
531 seq = new Sequence("", "FGHIJ");
532 seq.setDatasetSequence(ds2);
533 seqs = new SequenceI[]
535 e = command.new Edit(Action.DELETE_GAP, seqs, 4, 2, '-');
539 * One edit for the third sequence.
541 seq = new Sequence("", "MNOPQ");
542 SequenceI ds3 = new Sequence("", "MNOPQ");
543 seq.setDatasetSequence(ds3);
544 seqs = new SequenceI[]
546 e = command.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
549 Map<SequenceI, SequenceI> unwound = command.priorState(false);
550 assertEquals(original1, unwound.get(ds1).getSequenceAsString());
551 assertEquals(original2, unwound.get(ds2).getSequenceAsString());
552 assertEquals(original3, unwound.get(ds3).getSequenceAsString());
556 * Test that mimics 'remove all gapped columns' action. This generates a
557 * series Delete Gap edits that each act on all sequences that share a gapped
560 @Test(groups ={ "Functional" })
561 public void testPriorState_removeGappedCols()
563 EditCommand command = new EditCommand();
564 String original1 = "--ABC--DEF";
565 String original2 = "-G-HI--J";
566 String original3 = "-M-NO--PQ";
569 * First edit deletes the first column.
571 SequenceI seq1 = new Sequence("", "-ABC--DEF");
572 SequenceI ds1 = new Sequence("", "ABCDEF");
573 seq1.setDatasetSequence(ds1);
574 SequenceI seq2 = new Sequence("", "G-HI--J");
575 SequenceI ds2 = new Sequence("", "GHIJ");
576 seq2.setDatasetSequence(ds2);
577 SequenceI seq3 = new Sequence("", "M-NO--PQ");
578 SequenceI ds3 = new Sequence("", "MNOPQ");
579 seq3.setDatasetSequence(ds3);
580 SequenceI[] seqs = new SequenceI[]
581 { seq1, seq2, seq3 };
582 Edit e = command.new Edit(Action.DELETE_GAP, seqs, 0, 1, '-');
586 * Second edit deletes what is now columns 4 and 5.
588 seq1 = new Sequence("", "-ABCDEF");
589 seq1.setDatasetSequence(ds1);
590 seq2 = new Sequence("", "G-HIJ");
591 seq2.setDatasetSequence(ds2);
592 seq3 = new Sequence("", "M-NOPQ");
593 seq3.setDatasetSequence(ds3);
594 seqs = new SequenceI[]
595 { seq1, seq2, seq3 };
596 e = command.new Edit(Action.DELETE_GAP, seqs, 4, 2, '-');
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());
603 assertEquals(ds1, unwound.get(ds1).getDatasetSequence());
604 assertEquals(ds2, unwound.get(ds2).getDatasetSequence());
605 assertEquals(ds3, unwound.get(ds3).getDatasetSequence());