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[] { al });
89 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
90 assertEquals("fghjnopq", seqs[1].getSequenceAsString());
91 assertEquals("qrstxyz", seqs[2].getSequenceAsString());
92 assertEquals("1234890", seqs[3].getSequenceAsString());
94 assertEquals("efg", new String(ec.string[0]));
95 assertEquals("klm", new String(ec.string[1]));
96 assertEquals("uvw", new String(ec.string[2]));
97 assertEquals("567", new String(ec.string[3]));
98 // TODO: case where whole sequence is deleted as nothing left; etc
102 * Test a Paste action, where this adds sequences to an alignment.
104 @Test(groups = { "Functional" }, enabled = false)
105 // TODO fix so it works
106 public void testPaste_addToAlignment()
108 SequenceI[] newSeqs = new SequenceI[2];
109 newSeqs[0] = new Sequence("newseq0", "ACEFKL");
110 newSeqs[1] = new Sequence("newseq1", "JWMPDH");
112 Edit ec = testee.new Edit(Action.PASTE, newSeqs, 0, al.getWidth(), al);
113 testee.paste(ec, new AlignmentI[] { al });
114 assertEquals(6, al.getSequences().size());
115 assertEquals("1234567890", seqs[3].getSequenceAsString());
116 assertEquals("ACEFKL", seqs[4].getSequenceAsString());
117 assertEquals("JWMPDH", seqs[5].getSequenceAsString());
121 * Test insertGap followed by undo command
123 @Test(groups = { "Functional" })
124 public void testUndo_insertGap()
126 // Edit ec = testee.new Edit(Action.INSERT_GAP, seqs, 4, 3, '?');
127 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 3, al, true);
128 // check something changed
129 assertEquals("abcd???efghjk", seqs[0].getSequenceAsString());
130 testee.undoCommand(new AlignmentI[] { al });
131 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
132 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
133 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
134 assertEquals("1234567890", seqs[3].getSequenceAsString());
138 * Test deleteGap followed by undo command
140 @Test(groups = { "Functional" })
141 public void testUndo_deleteGap()
143 testee.appendEdit(Action.DELETE_GAP, seqs, 4, 3, al, true);
144 // check something changed
145 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
146 testee.undoCommand(new AlignmentI[] { al });
147 // deleteGap doesn't 'remember' deleted characters, only gaps get put back
148 assertEquals("abcd???hjk", seqs[0].getSequenceAsString());
149 assertEquals("fghj???nopq", seqs[1].getSequenceAsString());
150 assertEquals("qrst???xyz", seqs[2].getSequenceAsString());
151 assertEquals("1234???890", seqs[3].getSequenceAsString());
155 * Test several commands followed by an undo command
157 @Test(groups = { "Functional" })
158 public void testUndo_multipleCommands()
160 // delete positions 3/4/5 (counting from 1)
161 testee.appendEdit(Action.DELETE_GAP, seqs, 2, 3, al, true);
162 assertEquals("abfghjk", seqs[0].getSequenceAsString());
163 assertEquals("1267890", seqs[3].getSequenceAsString());
165 // insert 2 gaps after the second residue
166 testee.appendEdit(Action.INSERT_GAP, seqs, 2, 2, al, true);
167 assertEquals("ab??fghjk", seqs[0].getSequenceAsString());
168 assertEquals("12??67890", seqs[3].getSequenceAsString());
170 // delete positions 4/5/6
171 testee.appendEdit(Action.DELETE_GAP, seqs, 3, 3, al, true);
172 assertEquals("ab?hjk", seqs[0].getSequenceAsString());
173 assertEquals("12?890", seqs[3].getSequenceAsString());
175 // undo edit commands
176 testee.undoCommand(new AlignmentI[] { al });
177 assertEquals("ab?????hjk", seqs[0].getSequenceAsString());
178 assertEquals("12?????890", seqs[3].getSequenceAsString());
182 * Unit test for JAL-1594 bug: click and drag sequence right to insert gaps -
183 * undo did not remove them all.
185 @Test(groups = { "Functional" })
186 public void testUndo_multipleInsertGaps()
188 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 1, al, true);
189 testee.appendEdit(Action.INSERT_GAP, seqs, 5, 1, al, true);
190 testee.appendEdit(Action.INSERT_GAP, seqs, 6, 1, al, true);
192 // undo edit commands
193 testee.undoCommand(new AlignmentI[] { al });
194 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
195 assertEquals("1234567890", seqs[3].getSequenceAsString());
200 * Test cut followed by undo command
202 @Test(groups = { "Functional" })
203 public void testUndo_cut()
205 testee.appendEdit(Action.CUT, seqs, 4, 3, al, true);
206 // check something changed
207 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
208 testee.undoCommand(new AlignmentI[] { al });
209 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
210 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
211 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
212 assertEquals("1234567890", seqs[3].getSequenceAsString());
216 * Test the replace command (used to manually edit a sequence)
218 @Test(groups = { "Functional" })
219 public void testReplace()
221 // seem to need a dataset sequence on the edited sequence here
222 seqs[1].setDatasetSequence(seqs[1]);
223 new EditCommand("", Action.REPLACE, "ZXY", new SequenceI[] { seqs[1] },
225 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
226 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
227 assertEquals("1234567890", seqs[3].getSequenceAsString());
228 seqs[1] = new Sequence("seq1", "fghjZXYnopq");
232 * Test that the addEdit command correctly merges insert gap commands when
235 @Test(groups = { "Functional" })
236 public void testAddEdit_multipleInsertGap()
239 * 3 insert gap in a row (aka mouse drag right):
241 Edit e = new EditCommand().new Edit(Action.INSERT_GAP,
242 new SequenceI[] { seqs[0] }, 1, 1, al);
244 SequenceI edited = new Sequence("seq0", "a?bcdefghjk");
245 edited.setDatasetSequence(seqs[0].getDatasetSequence());
246 e = new EditCommand().new Edit(Action.INSERT_GAP,
247 new SequenceI[] { edited }, 2, 1, al);
249 edited = new Sequence("seq0", "a??bcdefghjk");
250 edited.setDatasetSequence(seqs[0].getDatasetSequence());
251 e = new EditCommand().new Edit(Action.INSERT_GAP,
252 new SequenceI[] { edited }, 3, 1, al);
254 assertEquals(1, testee.getSize());
255 assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
256 assertEquals(1, testee.getEdit(0).getPosition());
257 assertEquals(3, testee.getEdit(0).getNumber());
260 * Add a non-contiguous edit - should not be merged.
262 e = new EditCommand().new Edit(Action.INSERT_GAP,
263 new SequenceI[] { edited }, 5, 2, al);
265 assertEquals(2, testee.getSize());
266 assertEquals(5, testee.getEdit(1).getPosition());
267 assertEquals(2, testee.getEdit(1).getNumber());
270 * Add a Delete after the Insert - should not be merged.
272 e = new EditCommand().new Edit(Action.DELETE_GAP,
273 new SequenceI[] { edited }, 6, 2, al);
275 assertEquals(3, testee.getSize());
276 assertEquals(Action.DELETE_GAP, testee.getEdit(2).getAction());
277 assertEquals(6, testee.getEdit(2).getPosition());
278 assertEquals(2, testee.getEdit(2).getNumber());
282 * Test that the addEdit command correctly merges delete gap commands when
285 @Test(groups = { "Functional" })
286 public void testAddEdit_multipleDeleteGap()
289 * 3 delete gap in a row (aka mouse drag left):
291 seqs[0].setSequence("a???bcdefghjk");
292 Edit e = new EditCommand().new Edit(Action.DELETE_GAP,
293 new SequenceI[] { seqs[0] }, 4, 1, al);
295 assertEquals(1, testee.getSize());
297 SequenceI edited = new Sequence("seq0", "a??bcdefghjk");
298 edited.setDatasetSequence(seqs[0].getDatasetSequence());
299 e = new EditCommand().new Edit(Action.DELETE_GAP,
300 new SequenceI[] { edited }, 3, 1, al);
302 assertEquals(1, testee.getSize());
304 edited = new Sequence("seq0", "a?bcdefghjk");
305 edited.setDatasetSequence(seqs[0].getDatasetSequence());
306 e = new EditCommand().new Edit(Action.DELETE_GAP,
307 new SequenceI[] { edited }, 2, 1, al);
309 assertEquals(1, testee.getSize());
310 assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
311 assertEquals(2, testee.getEdit(0).getPosition());
312 assertEquals(3, testee.getEdit(0).getNumber());
315 * Add a non-contiguous edit - should not be merged.
317 e = new EditCommand().new Edit(Action.DELETE_GAP,
318 new SequenceI[] { edited }, 2, 1, al);
320 assertEquals(2, testee.getSize());
321 assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
322 assertEquals(2, testee.getEdit(1).getPosition());
323 assertEquals(1, testee.getEdit(1).getNumber());
326 * Add an Insert after the Delete - should not be merged.
328 e = new EditCommand().new Edit(Action.INSERT_GAP,
329 new SequenceI[] { edited }, 1, 1, al);
331 assertEquals(3, testee.getSize());
332 assertEquals(Action.INSERT_GAP, testee.getEdit(2).getAction());
333 assertEquals(1, testee.getEdit(2).getPosition());
334 assertEquals(1, testee.getEdit(2).getNumber());
338 * Test that the addEdit command correctly handles 'remove gaps' edits for the
339 * case when they appear contiguous but are acting on different sequences.
340 * They should not be merged.
342 @Test(groups = { "Functional" })
343 public void testAddEdit_removeAllGaps()
345 seqs[0].setSequence("a???bcdefghjk");
346 Edit e = new EditCommand().new Edit(Action.DELETE_GAP,
347 new SequenceI[] { seqs[0] }, 4, 1, al);
350 seqs[1].setSequence("f??ghjklmnopq");
351 Edit e2 = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
352 { seqs[1] }, 3, 1, al);
354 assertEquals(2, testee.getSize());
355 assertSame(e, testee.getEdit(0));
356 assertSame(e2, testee.getEdit(1));
360 * Test that the addEdit command correctly merges insert gap commands acting
361 * on a multi-sequence selection.
363 @Test(groups = { "Functional" })
364 public void testAddEdit_groupInsertGaps()
367 * 2 insert gap in a row (aka mouse drag right), on two sequences:
369 Edit e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] {
370 seqs[0], seqs[1] }, 1, 1, al);
372 SequenceI seq1edited = new Sequence("seq0", "a?bcdefghjk");
373 seq1edited.setDatasetSequence(seqs[0].getDatasetSequence());
374 SequenceI seq2edited = new Sequence("seq1", "f?ghjklmnopq");
375 seq2edited.setDatasetSequence(seqs[1].getDatasetSequence());
376 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] {
377 seq1edited, seq2edited }, 2, 1, al);
380 assertEquals(1, testee.getSize());
381 assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
382 assertEquals(1, testee.getEdit(0).getPosition());
383 assertEquals(2, testee.getEdit(0).getNumber());
384 assertEquals(seqs[0].getDatasetSequence(), testee.getEdit(0)
385 .getSequences()[0].getDatasetSequence());
386 assertEquals(seqs[1].getDatasetSequence(), testee.getEdit(0)
387 .getSequences()[1].getDatasetSequence());
391 * Test for 'undoing' a series of gap insertions.
393 * <li>Start: ABCDEF insert 2 at pos 1</li>
394 * <li>next: A--BCDEF insert 1 at pos 4</li>
395 * <li>next: A--B-CDEF insert 2 at pos 0</li>
396 * <li>last: --A--B-CDEF</li>
399 @Test(groups = { "Functional" })
400 public void testPriorState_multipleInserts()
402 EditCommand command = new EditCommand();
403 SequenceI seq = new Sequence("", "--A--B-CDEF");
404 SequenceI ds = new Sequence("", "ABCDEF");
405 seq.setDatasetSequence(ds);
406 SequenceI[] seqs = new SequenceI[] { seq };
407 Edit e = command.new Edit(Action.INSERT_GAP, seqs, 1, 2, '-');
409 e = command.new Edit(Action.INSERT_GAP, seqs, 4, 1, '-');
411 e = command.new Edit(Action.INSERT_GAP, seqs, 0, 2, '-');
414 Map<SequenceI, SequenceI> unwound = command.priorState(false);
415 assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString());
419 * Test for 'undoing' a series of gap deletions.
421 * <li>Start: A-B-C delete 1 at pos 1</li>
422 * <li>Next: AB-C delete 1 at pos 2</li>
426 @Test(groups = { "Functional" })
427 public void testPriorState_removeAllGaps()
429 EditCommand command = new EditCommand();
430 SequenceI seq = new Sequence("", "ABC");
431 SequenceI ds = new Sequence("", "ABC");
432 seq.setDatasetSequence(ds);
433 SequenceI[] seqs = new SequenceI[] { seq };
434 Edit e = command.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
436 e = command.new Edit(Action.DELETE_GAP, seqs, 2, 1, '-');
439 Map<SequenceI, SequenceI> unwound = command.priorState(false);
440 assertEquals("A-B-C", unwound.get(ds).getSequenceAsString());
444 * Test for 'undoing' a single delete edit.
446 @Test(groups = { "Functional" })
447 public void testPriorState_singleDelete()
449 EditCommand command = new EditCommand();
450 SequenceI seq = new Sequence("", "ABCDEF");
451 SequenceI ds = new Sequence("", "ABCDEF");
452 seq.setDatasetSequence(ds);
453 SequenceI[] seqs = new SequenceI[] { seq };
454 Edit e = command.new Edit(Action.DELETE_GAP, seqs, 2, 2, '-');
457 Map<SequenceI, SequenceI> unwound = command.priorState(false);
458 assertEquals("AB--CDEF", unwound.get(ds).getSequenceAsString());
462 * Test 'undoing' a single gap insertion edit command.
464 @Test(groups = { "Functional" })
465 public void testPriorState_singleInsert()
467 EditCommand command = new EditCommand();
468 SequenceI seq = new Sequence("", "AB---CDEF");
469 SequenceI ds = new Sequence("", "ABCDEF");
470 seq.setDatasetSequence(ds);
471 SequenceI[] seqs = new SequenceI[] { seq };
472 Edit e = command.new Edit(Action.INSERT_GAP, seqs, 2, 3, '-');
475 Map<SequenceI, SequenceI> unwound = command.priorState(false);
476 assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString());
480 * Test that mimics 'remove all gaps' action. This generates delete gap edits
481 * for contiguous gaps in each sequence separately.
483 @Test(groups = { "Functional" })
484 public void testPriorState_removeGapsMultipleSeqs()
486 EditCommand command = new EditCommand();
487 String original1 = "--ABC-DEF";
488 String original2 = "FG-HI--J";
489 String original3 = "M-NOPQ";
492 * Two edits for the first sequence
494 SequenceI seq = new Sequence("", "ABC-DEF");
495 SequenceI ds1 = new Sequence("", "ABCDEF");
496 seq.setDatasetSequence(ds1);
497 SequenceI[] seqs = new SequenceI[] { seq };
498 Edit e = command.new Edit(Action.DELETE_GAP, seqs, 0, 2, '-');
500 seq = new Sequence("", "ABCDEF");
501 seq.setDatasetSequence(ds1);
502 seqs = new SequenceI[] { seq };
503 e = command.new Edit(Action.DELETE_GAP, seqs, 3, 1, '-');
507 * Two edits for the second sequence
509 seq = new Sequence("", "FGHI--J");
510 SequenceI ds2 = new Sequence("", "FGHIJ");
511 seq.setDatasetSequence(ds2);
512 seqs = new SequenceI[] { seq };
513 e = command.new Edit(Action.DELETE_GAP, seqs, 2, 1, '-');
515 seq = new Sequence("", "FGHIJ");
516 seq.setDatasetSequence(ds2);
517 seqs = new SequenceI[] { seq };
518 e = command.new Edit(Action.DELETE_GAP, seqs, 4, 2, '-');
522 * One edit for the third sequence.
524 seq = new Sequence("", "MNOPQ");
525 SequenceI ds3 = new Sequence("", "MNOPQ");
526 seq.setDatasetSequence(ds3);
527 seqs = new SequenceI[] { seq };
528 e = command.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
531 Map<SequenceI, SequenceI> unwound = command.priorState(false);
532 assertEquals(original1, unwound.get(ds1).getSequenceAsString());
533 assertEquals(original2, unwound.get(ds2).getSequenceAsString());
534 assertEquals(original3, unwound.get(ds3).getSequenceAsString());
538 * Test that mimics 'remove all gapped columns' action. This generates a
539 * series Delete Gap edits that each act on all sequences that share a gapped
542 @Test(groups = { "Functional" })
543 public void testPriorState_removeGappedCols()
545 EditCommand command = new EditCommand();
546 String original1 = "--ABC--DEF";
547 String original2 = "-G-HI--J";
548 String original3 = "-M-NO--PQ";
551 * First edit deletes the first column.
553 SequenceI seq1 = new Sequence("", "-ABC--DEF");
554 SequenceI ds1 = new Sequence("", "ABCDEF");
555 seq1.setDatasetSequence(ds1);
556 SequenceI seq2 = new Sequence("", "G-HI--J");
557 SequenceI ds2 = new Sequence("", "GHIJ");
558 seq2.setDatasetSequence(ds2);
559 SequenceI seq3 = new Sequence("", "M-NO--PQ");
560 SequenceI ds3 = new Sequence("", "MNOPQ");
561 seq3.setDatasetSequence(ds3);
562 SequenceI[] seqs = new SequenceI[] { seq1, seq2, seq3 };
563 Edit e = command.new Edit(Action.DELETE_GAP, seqs, 0, 1, '-');
567 * Second edit deletes what is now columns 4 and 5.
569 seq1 = new Sequence("", "-ABCDEF");
570 seq1.setDatasetSequence(ds1);
571 seq2 = new Sequence("", "G-HIJ");
572 seq2.setDatasetSequence(ds2);
573 seq3 = new Sequence("", "M-NOPQ");
574 seq3.setDatasetSequence(ds3);
575 seqs = new SequenceI[] { seq1, seq2, seq3 };
576 e = command.new Edit(Action.DELETE_GAP, seqs, 4, 2, '-');
579 Map<SequenceI, SequenceI> unwound = command.priorState(false);
580 assertEquals(original1, unwound.get(ds1).getSequenceAsString());
581 assertEquals(original2, unwound.get(ds2).getSequenceAsString());
582 assertEquals(original3, unwound.get(ds3).getSequenceAsString());
583 assertEquals(ds1, unwound.get(ds1).getDatasetSequence());
584 assertEquals(ds2, unwound.get(ds2).getDatasetSequence());
585 assertEquals(ds3, unwound.get(ds3).getDatasetSequence());