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(alwaysRun = true)
36 testee = new EditCommand();
37 seqs = new SequenceI[4];
38 seqs[0] = new Sequence("seq0", "abcdefghjk");
39 seqs[0].setDatasetSequence(new Sequence("seq0ds", "abcdefghjk"));
40 seqs[1] = new Sequence("seq1", "fghjklmnopq");
41 seqs[1].setDatasetSequence(new Sequence("seq1ds", "fghjklmnopq"));
42 seqs[2] = new Sequence("seq2", "qrstuvwxyz");
43 seqs[2].setDatasetSequence(new Sequence("seq2ds", "qrstuvwxyz"));
44 seqs[3] = new Sequence("seq3", "1234567890");
45 seqs[3].setDatasetSequence(new Sequence("seq3ds", "1234567890"));
46 al = new Alignment(seqs);
47 al.setGapCharacter('?');
51 * Test inserting gap characters
53 @Test(groups ={ "Functional" })
54 public void testAppendEdit_insertGap()
56 // set a non-standard gap character to prove it is actually used
57 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 3, al, true);
58 assertEquals("abcd???efghjk", seqs[0].getSequenceAsString());
59 assertEquals("fghj???klmnopq", seqs[1].getSequenceAsString());
60 assertEquals("qrst???uvwxyz", seqs[2].getSequenceAsString());
61 assertEquals("1234???567890", seqs[3].getSequenceAsString());
63 // todo: test for handling out of range positions?
67 * Test deleting characters from sequences. Note the deleteGap() action does
68 * not check that only gap characters are being removed.
70 @Test(groups ={ "Functional" })
71 public void testAppendEdit_deleteGap()
73 testee.appendEdit(Action.DELETE_GAP, seqs, 4, 3, al, true);
74 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
75 assertEquals("fghjnopq", seqs[1].getSequenceAsString());
76 assertEquals("qrstxyz", seqs[2].getSequenceAsString());
77 assertEquals("1234890", seqs[3].getSequenceAsString());
81 * Test a cut action. The command should store the cut characters to support
84 @Test(groups ={ "Functional" })
87 Edit ec = testee.new Edit(Action.CUT, seqs, 4, 3, al);
88 testee.cut(ec, new AlignmentI[]
90 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
91 assertEquals("fghjnopq", seqs[1].getSequenceAsString());
92 assertEquals("qrstxyz", seqs[2].getSequenceAsString());
93 assertEquals("1234890", seqs[3].getSequenceAsString());
95 assertEquals("efg", new String(ec.string[0]));
96 assertEquals("klm", new String(ec.string[1]));
97 assertEquals("uvw", new String(ec.string[2]));
98 assertEquals("567", new String(ec.string[3]));
99 // TODO: case where whole sequence is deleted as nothing left; etc
103 * Test a Paste action, where this adds sequences to an alignment.
106 { "Functional" }, enabled = false)
107 // TODO fix so it works
108 public void testPaste_addToAlignment()
110 SequenceI[] newSeqs = new SequenceI[2];
111 newSeqs[0] = new Sequence("newseq0", "ACEFKL");
112 newSeqs[1] = new Sequence("newseq1", "JWMPDH");
114 Edit ec = testee.new Edit(Action.PASTE, newSeqs, 0, al.getWidth(), al);
115 testee.paste(ec, new AlignmentI[]
117 assertEquals(6, al.getSequences().size());
118 assertEquals("1234567890", seqs[3].getSequenceAsString());
119 assertEquals("ACEFKL", seqs[4].getSequenceAsString());
120 assertEquals("JWMPDH", seqs[5].getSequenceAsString());
124 * Test insertGap followed by undo command
126 @Test(groups ={ "Functional" })
127 public void testUndo_insertGap()
129 // Edit ec = testee.new Edit(Action.INSERT_GAP, seqs, 4, 3, '?');
130 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 3, al, true);
131 // check something changed
132 assertEquals("abcd???efghjk", seqs[0].getSequenceAsString());
133 testee.undoCommand(new AlignmentI[]
135 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
136 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
137 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
138 assertEquals("1234567890", seqs[3].getSequenceAsString());
142 * Test deleteGap followed by undo command
144 @Test(groups ={ "Functional" })
145 public void testUndo_deleteGap()
147 testee.appendEdit(Action.DELETE_GAP, seqs, 4, 3, al, true);
148 // check something changed
149 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
150 testee.undoCommand(new AlignmentI[]
152 // deleteGap doesn't 'remember' deleted characters, only gaps get put back
153 assertEquals("abcd???hjk", seqs[0].getSequenceAsString());
154 assertEquals("fghj???nopq", seqs[1].getSequenceAsString());
155 assertEquals("qrst???xyz", seqs[2].getSequenceAsString());
156 assertEquals("1234???890", seqs[3].getSequenceAsString());
160 * Test several commands followed by an undo command
162 @Test(groups ={ "Functional" })
163 public void testUndo_multipleCommands()
165 // delete positions 3/4/5 (counting from 1)
166 testee.appendEdit(Action.DELETE_GAP, seqs, 2, 3, al, true);
167 assertEquals("abfghjk", seqs[0].getSequenceAsString());
168 assertEquals("1267890", seqs[3].getSequenceAsString());
170 // insert 2 gaps after the second residue
171 testee.appendEdit(Action.INSERT_GAP, seqs, 2, 2, al, true);
172 assertEquals("ab??fghjk", seqs[0].getSequenceAsString());
173 assertEquals("12??67890", seqs[3].getSequenceAsString());
175 // delete positions 4/5/6
176 testee.appendEdit(Action.DELETE_GAP, seqs, 3, 3, al, true);
177 assertEquals("ab?hjk", seqs[0].getSequenceAsString());
178 assertEquals("12?890", seqs[3].getSequenceAsString());
180 // undo edit commands
181 testee.undoCommand(new AlignmentI[]
183 assertEquals("ab?????hjk", seqs[0].getSequenceAsString());
184 assertEquals("12?????890", seqs[3].getSequenceAsString());
188 * Unit test for JAL-1594 bug: click and drag sequence right to insert gaps -
189 * undo did not remove them all.
191 @Test(groups ={ "Functional" })
192 public void testUndo_multipleInsertGaps()
194 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 1, al, true);
195 testee.appendEdit(Action.INSERT_GAP, seqs, 5, 1, al, true);
196 testee.appendEdit(Action.INSERT_GAP, seqs, 6, 1, al, true);
198 // undo edit commands
199 testee.undoCommand(new AlignmentI[]
201 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
202 assertEquals("1234567890", seqs[3].getSequenceAsString());
207 * Test cut followed by undo command
209 @Test(groups ={ "Functional" })
210 public void testUndo_cut()
212 testee.appendEdit(Action.CUT, seqs, 4, 3, al, true);
213 // check something changed
214 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
215 testee.undoCommand(new AlignmentI[]
217 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
218 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
219 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
220 assertEquals("1234567890", seqs[3].getSequenceAsString());
224 * Test the replace command (used to manually edit a sequence)
226 @Test(groups ={ "Functional" })
227 public void testReplace()
229 // seem to need a dataset sequence on the edited sequence here
230 seqs[1].setDatasetSequence(seqs[1]);
231 new EditCommand("", Action.REPLACE, "ZXY", new SequenceI[]
232 { seqs[1] }, 4, 8, al);
233 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
234 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
235 assertEquals("1234567890", seqs[3].getSequenceAsString());
236 seqs[1] = new Sequence("seq1", "fghjZXYnopq");
240 * Test that the addEdit command correctly merges insert gap commands when
243 @Test(groups ={ "Functional" })
244 public void testAddEdit_multipleInsertGap()
247 * 3 insert gap in a row (aka mouse drag right):
249 Edit e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
250 { seqs[0] }, 1, 1, al);
252 SequenceI edited = new Sequence("seq0", "a?bcdefghjk");
253 edited.setDatasetSequence(seqs[0].getDatasetSequence());
254 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
255 { edited }, 2, 1, al);
257 edited = new Sequence("seq0", "a??bcdefghjk");
258 edited.setDatasetSequence(seqs[0].getDatasetSequence());
259 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
260 { edited }, 3, 1, al);
262 assertEquals(1, testee.getSize());
263 assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
264 assertEquals(1, testee.getEdit(0).getPosition());
265 assertEquals(3, testee.getEdit(0).getNumber());
268 * Add a non-contiguous edit - should not be merged.
270 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
271 { edited }, 5, 2, al);
273 assertEquals(2, testee.getSize());
274 assertEquals(5, testee.getEdit(1).getPosition());
275 assertEquals(2, testee.getEdit(1).getNumber());
278 * Add a Delete after the Insert - should not be merged.
280 e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
281 { edited }, 6, 2, al);
283 assertEquals(3, testee.getSize());
284 assertEquals(Action.DELETE_GAP, testee.getEdit(2).getAction());
285 assertEquals(6, testee.getEdit(2).getPosition());
286 assertEquals(2, testee.getEdit(2).getNumber());
290 * Test that the addEdit command correctly merges delete gap commands when
293 @Test(groups ={ "Functional" })
294 public void testAddEdit_multipleDeleteGap()
297 * 3 delete gap in a row (aka mouse drag left):
299 seqs[0].setSequence("a???bcdefghjk");
300 Edit e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
301 { seqs[0] }, 4, 1, al);
303 assertEquals(1, testee.getSize());
305 SequenceI edited = new Sequence("seq0", "a??bcdefghjk");
306 edited.setDatasetSequence(seqs[0].getDatasetSequence());
307 e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
308 { edited }, 3, 1, al);
310 assertEquals(1, testee.getSize());
312 edited = new Sequence("seq0", "a?bcdefghjk");
313 edited.setDatasetSequence(seqs[0].getDatasetSequence());
314 e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
315 { edited }, 2, 1, al);
317 assertEquals(1, testee.getSize());
318 assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
319 assertEquals(2, testee.getEdit(0).getPosition());
320 assertEquals(3, testee.getEdit(0).getNumber());
323 * Add a non-contiguous edit - should not be merged.
325 e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
326 { edited }, 2, 1, al);
328 assertEquals(2, testee.getSize());
329 assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
330 assertEquals(2, testee.getEdit(1).getPosition());
331 assertEquals(1, testee.getEdit(1).getNumber());
334 * Add an Insert after the Delete - should not be merged.
336 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
337 { edited }, 1, 1, al);
339 assertEquals(3, testee.getSize());
340 assertEquals(Action.INSERT_GAP, testee.getEdit(2).getAction());
341 assertEquals(1, testee.getEdit(2).getPosition());
342 assertEquals(1, testee.getEdit(2).getNumber());
346 * Test that the addEdit command correctly handles 'remove gaps' edits for the
347 * case when they appear contiguous but are acting on different sequences.
348 * They should not be merged.
350 @Test(groups ={ "Functional" })
351 public void testAddEdit_removeAllGaps()
353 seqs[0].setSequence("a???bcdefghjk");
354 Edit e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
355 { seqs[0] }, 4, 1, al);
358 seqs[1].setSequence("f??ghjklmnopq");
359 Edit e2 = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
360 { seqs[1] }, 3, 1, al);
362 assertEquals(2, testee.getSize());
363 assertSame(e, testee.getEdit(0));
364 assertSame(e2, testee.getEdit(1));
368 * Test that the addEdit command correctly merges insert gap commands acting
369 * on a multi-sequence selection.
371 @Test(groups ={ "Functional" })
372 public void testAddEdit_groupInsertGaps()
375 * 2 insert gap in a row (aka mouse drag right), on two sequences:
377 Edit e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
378 { seqs[0], seqs[1] }, 1, 1, al);
380 SequenceI seq1edited = new Sequence("seq0", "a?bcdefghjk");
381 seq1edited.setDatasetSequence(seqs[0].getDatasetSequence());
382 SequenceI seq2edited = new Sequence("seq1", "f?ghjklmnopq");
383 seq2edited.setDatasetSequence(seqs[1].getDatasetSequence());
384 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
385 { seq1edited, seq2edited }, 2, 1, al);
388 assertEquals(1, testee.getSize());
389 assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
390 assertEquals(1, testee.getEdit(0).getPosition());
391 assertEquals(2, testee.getEdit(0).getNumber());
392 assertEquals(seqs[0].getDatasetSequence(), testee.getEdit(0)
393 .getSequences()[0].getDatasetSequence());
394 assertEquals(seqs[1].getDatasetSequence(), testee.getEdit(0)
395 .getSequences()[1].getDatasetSequence());
399 * Test for 'undoing' a series of gap insertions.
401 * <li>Start: ABCDEF insert 2 at pos 1</li>
402 * <li>next: A--BCDEF insert 1 at pos 4</li>
403 * <li>next: A--B-CDEF insert 2 at pos 0</li>
404 * <li>last: --A--B-CDEF</li>
407 @Test(groups ={ "Functional" })
408 public void testPriorState_multipleInserts()
410 EditCommand command = new EditCommand();
411 SequenceI seq = new Sequence("", "--A--B-CDEF");
412 SequenceI ds = new Sequence("", "ABCDEF");
413 seq.setDatasetSequence(ds);
414 SequenceI[] seqs = new SequenceI[]
416 Edit e = command.new Edit(Action.INSERT_GAP, seqs, 1, 2, '-');
418 e = command.new Edit(Action.INSERT_GAP, seqs, 4, 1, '-');
420 e = command.new Edit(Action.INSERT_GAP, seqs, 0, 2, '-');
423 Map<SequenceI, SequenceI> unwound = command.priorState(false);
424 assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString());
428 * Test for 'undoing' a series of gap deletions.
430 * <li>Start: A-B-C delete 1 at pos 1</li>
431 * <li>Next: AB-C delete 1 at pos 2</li>
435 @Test(groups ={ "Functional" })
436 public void testPriorState_removeAllGaps()
438 EditCommand command = new EditCommand();
439 SequenceI seq = new Sequence("", "ABC");
440 SequenceI ds = new Sequence("", "ABC");
441 seq.setDatasetSequence(ds);
442 SequenceI[] seqs = new SequenceI[]
444 Edit e = command.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
446 e = command.new Edit(Action.DELETE_GAP, seqs, 2, 1, '-');
449 Map<SequenceI, SequenceI> unwound = command.priorState(false);
450 assertEquals("A-B-C", unwound.get(ds).getSequenceAsString());
454 * Test for 'undoing' a single delete edit.
456 @Test(groups ={ "Functional" })
457 public void testPriorState_singleDelete()
459 EditCommand command = new EditCommand();
460 SequenceI seq = new Sequence("", "ABCDEF");
461 SequenceI ds = new Sequence("", "ABCDEF");
462 seq.setDatasetSequence(ds);
463 SequenceI[] seqs = new SequenceI[]
465 Edit e = command.new Edit(Action.DELETE_GAP, seqs, 2, 2, '-');
468 Map<SequenceI, SequenceI> unwound = command.priorState(false);
469 assertEquals("AB--CDEF", unwound.get(ds).getSequenceAsString());
473 * Test 'undoing' a single gap insertion edit command.
475 @Test(groups ={ "Functional" })
476 public void testPriorState_singleInsert()
478 EditCommand command = new EditCommand();
479 SequenceI seq = new Sequence("", "AB---CDEF");
480 SequenceI ds = new Sequence("", "ABCDEF");
481 seq.setDatasetSequence(ds);
482 SequenceI[] seqs = new SequenceI[]
484 Edit e = command.new Edit(Action.INSERT_GAP, seqs, 2, 3, '-');
487 Map<SequenceI, SequenceI> unwound = command.priorState(false);
488 assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString());
492 * Test that mimics 'remove all gaps' action. This generates delete gap edits
493 * for contiguous gaps in each sequence separately.
495 @Test(groups ={ "Functional" })
496 public void testPriorState_removeGapsMultipleSeqs()
498 EditCommand command = new EditCommand();
499 String original1 = "--ABC-DEF";
500 String original2 = "FG-HI--J";
501 String original3 = "M-NOPQ";
504 * Two edits for the first sequence
506 SequenceI seq = new Sequence("", "ABC-DEF");
507 SequenceI ds1 = new Sequence("", "ABCDEF");
508 seq.setDatasetSequence(ds1);
509 SequenceI[] seqs = new SequenceI[]
511 Edit e = command.new Edit(Action.DELETE_GAP, seqs, 0, 2, '-');
513 seq = new Sequence("", "ABCDEF");
514 seq.setDatasetSequence(ds1);
515 seqs = new SequenceI[]
517 e = command.new Edit(Action.DELETE_GAP, seqs, 3, 1, '-');
521 * Two edits for the second sequence
523 seq = new Sequence("", "FGHI--J");
524 SequenceI ds2 = new Sequence("", "FGHIJ");
525 seq.setDatasetSequence(ds2);
526 seqs = new SequenceI[]
528 e = command.new Edit(Action.DELETE_GAP, seqs, 2, 1, '-');
530 seq = new Sequence("", "FGHIJ");
531 seq.setDatasetSequence(ds2);
532 seqs = new SequenceI[]
534 e = command.new Edit(Action.DELETE_GAP, seqs, 4, 2, '-');
538 * One edit for the third sequence.
540 seq = new Sequence("", "MNOPQ");
541 SequenceI ds3 = new Sequence("", "MNOPQ");
542 seq.setDatasetSequence(ds3);
543 seqs = new SequenceI[]
545 e = command.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
548 Map<SequenceI, SequenceI> unwound = command.priorState(false);
549 assertEquals(original1, unwound.get(ds1).getSequenceAsString());
550 assertEquals(original2, unwound.get(ds2).getSequenceAsString());
551 assertEquals(original3, unwound.get(ds3).getSequenceAsString());
555 * Test that mimics 'remove all gapped columns' action. This generates a
556 * series Delete Gap edits that each act on all sequences that share a gapped
559 @Test(groups ={ "Functional" })
560 public void testPriorState_removeGappedCols()
562 EditCommand command = new EditCommand();
563 String original1 = "--ABC--DEF";
564 String original2 = "-G-HI--J";
565 String original3 = "-M-NO--PQ";
568 * First edit deletes the first column.
570 SequenceI seq1 = new Sequence("", "-ABC--DEF");
571 SequenceI ds1 = new Sequence("", "ABCDEF");
572 seq1.setDatasetSequence(ds1);
573 SequenceI seq2 = new Sequence("", "G-HI--J");
574 SequenceI ds2 = new Sequence("", "GHIJ");
575 seq2.setDatasetSequence(ds2);
576 SequenceI seq3 = new Sequence("", "M-NO--PQ");
577 SequenceI ds3 = new Sequence("", "MNOPQ");
578 seq3.setDatasetSequence(ds3);
579 SequenceI[] seqs = new SequenceI[]
580 { seq1, seq2, seq3 };
581 Edit e = command.new Edit(Action.DELETE_GAP, seqs, 0, 1, '-');
585 * Second edit deletes what is now columns 4 and 5.
587 seq1 = new Sequence("", "-ABCDEF");
588 seq1.setDatasetSequence(ds1);
589 seq2 = new Sequence("", "G-HIJ");
590 seq2.setDatasetSequence(ds2);
591 seq3 = new Sequence("", "M-NOPQ");
592 seq3.setDatasetSequence(ds3);
593 seqs = new SequenceI[]
594 { seq1, seq2, seq3 };
595 e = command.new Edit(Action.DELETE_GAP, seqs, 4, 2, '-');
598 Map<SequenceI, SequenceI> unwound = command.priorState(false);
599 assertEquals(original1, unwound.get(ds1).getSequenceAsString());
600 assertEquals(original2, unwound.get(ds2).getSequenceAsString());
601 assertEquals(original3, unwound.get(ds3).getSequenceAsString());
602 assertEquals(ds1, unwound.get(ds1).getDatasetSequence());
603 assertEquals(ds2, unwound.get(ds2).getDatasetSequence());
604 assertEquals(ds3, unwound.get(ds3).getDatasetSequence());