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;
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
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.
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
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.
105 @Test(enabled = false)
106 // TODO fix so it works
107 public void testPaste_addToAlignment()
109 SequenceI[] newSeqs = new SequenceI[2];
110 newSeqs[0] = new Sequence("newseq0", "ACEFKL");
111 newSeqs[1] = new Sequence("newseq1", "JWMPDH");
113 Edit ec = testee.new Edit(Action.PASTE, newSeqs, 0, al.getWidth(), al);
114 testee.paste(ec, new AlignmentI[]
116 assertEquals(6, al.getSequences().size());
117 assertEquals("1234567890", seqs[3].getSequenceAsString());
118 assertEquals("ACEFKL", seqs[4].getSequenceAsString());
119 assertEquals("JWMPDH", seqs[5].getSequenceAsString());
123 * Test insertGap followed by undo command
126 public void testUndo_insertGap()
128 // Edit ec = testee.new Edit(Action.INSERT_GAP, seqs, 4, 3, '?');
129 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 3, al, true);
130 // check something changed
131 assertEquals("abcd???efghjk", seqs[0].getSequenceAsString());
132 testee.undoCommand(new AlignmentI[]
134 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
135 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
136 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
137 assertEquals("1234567890", seqs[3].getSequenceAsString());
141 * Test deleteGap followed by undo command
144 public void testUndo_deleteGap()
146 testee.appendEdit(Action.DELETE_GAP, seqs, 4, 3, al, true);
147 // check something changed
148 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
149 testee.undoCommand(new AlignmentI[]
151 // deleteGap doesn't 'remember' deleted characters, only gaps get put back
152 assertEquals("abcd???hjk", seqs[0].getSequenceAsString());
153 assertEquals("fghj???nopq", seqs[1].getSequenceAsString());
154 assertEquals("qrst???xyz", seqs[2].getSequenceAsString());
155 assertEquals("1234???890", seqs[3].getSequenceAsString());
159 * Test several commands followed by an undo command
162 public void testUndo_multipleCommands()
164 // delete positions 3/4/5 (counting from 1)
165 testee.appendEdit(Action.DELETE_GAP, seqs, 2, 3, al, true);
166 assertEquals("abfghjk", seqs[0].getSequenceAsString());
167 assertEquals("1267890", seqs[3].getSequenceAsString());
169 // insert 2 gaps after the second residue
170 testee.appendEdit(Action.INSERT_GAP, seqs, 2, 2, al, true);
171 assertEquals("ab??fghjk", seqs[0].getSequenceAsString());
172 assertEquals("12??67890", seqs[3].getSequenceAsString());
174 // delete positions 4/5/6
175 testee.appendEdit(Action.DELETE_GAP, seqs, 3, 3, al, true);
176 assertEquals("ab?hjk", seqs[0].getSequenceAsString());
177 assertEquals("12?890", seqs[3].getSequenceAsString());
179 // undo edit commands
180 testee.undoCommand(new AlignmentI[]
182 assertEquals("ab?????hjk", seqs[0].getSequenceAsString());
183 assertEquals("12?????890", seqs[3].getSequenceAsString());
187 * Unit test for JAL-1594 bug: click and drag sequence right to insert gaps -
188 * undo did not remove them all.
191 public void testUndo_multipleInsertGaps()
193 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 1, al, true);
194 testee.appendEdit(Action.INSERT_GAP, seqs, 5, 1, al, true);
195 testee.appendEdit(Action.INSERT_GAP, seqs, 6, 1, al, true);
197 // undo edit commands
198 testee.undoCommand(new AlignmentI[]
200 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
201 assertEquals("1234567890", seqs[3].getSequenceAsString());
206 * Test cut followed by undo command
209 public void testUndo_cut()
211 testee.appendEdit(Action.CUT, seqs, 4, 3, al, true);
212 // check something changed
213 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
214 testee.undoCommand(new AlignmentI[]
216 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
217 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
218 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
219 assertEquals("1234567890", seqs[3].getSequenceAsString());
223 * Test the replace command (used to manually edit a sequence)
226 public void testReplace()
228 // seem to need a dataset sequence on the edited sequence here
229 seqs[1].setDatasetSequence(seqs[1]);
230 new EditCommand("", Action.REPLACE, "ZXY", new SequenceI[]
231 { seqs[1] }, 4, 8, al);
232 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
233 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
234 assertEquals("1234567890", seqs[3].getSequenceAsString());
235 seqs[1] = new Sequence("seq1", "fghjZXYnopq");
239 * Test that the addEdit command correctly merges insert gap commands when
243 public void testAddEdit_multipleInsertGap()
246 * 3 insert gap in a row (aka mouse drag right):
248 Edit e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
249 { seqs[0] }, 1, 1, al);
251 SequenceI edited = new Sequence("seq0", "a?bcdefghjk");
252 edited.setDatasetSequence(seqs[0].getDatasetSequence());
253 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
254 { edited }, 2, 1, al);
256 edited = new Sequence("seq0", "a??bcdefghjk");
257 edited.setDatasetSequence(seqs[0].getDatasetSequence());
258 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
259 { edited }, 3, 1, al);
261 assertEquals(1, testee.getSize());
262 assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
263 assertEquals(1, testee.getEdit(0).getPosition());
264 assertEquals(3, testee.getEdit(0).getNumber());
267 * Add a non-contiguous edit - should not be merged.
269 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
270 { edited }, 5, 2, al);
272 assertEquals(2, testee.getSize());
273 assertEquals(5, testee.getEdit(1).getPosition());
274 assertEquals(2, testee.getEdit(1).getNumber());
277 * Add a Delete after the Insert - should not be merged.
279 e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
280 { edited }, 6, 2, al);
282 assertEquals(3, testee.getSize());
283 assertEquals(Action.DELETE_GAP, testee.getEdit(2).getAction());
284 assertEquals(6, testee.getEdit(2).getPosition());
285 assertEquals(2, testee.getEdit(2).getNumber());
289 * Test that the addEdit command correctly merges delete gap commands when
293 public void testAddEdit_multipleDeleteGap()
296 * 3 delete gap in a row (aka mouse drag left):
298 seqs[0].setSequence("a???bcdefghjk");
299 Edit e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
300 { seqs[0] }, 4, 1, al);
302 assertEquals(1, testee.getSize());
304 SequenceI edited = new Sequence("seq0", "a??bcdefghjk");
305 edited.setDatasetSequence(seqs[0].getDatasetSequence());
306 e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
307 { edited }, 3, 1, al);
309 assertEquals(1, testee.getSize());
311 edited = new Sequence("seq0", "a?bcdefghjk");
312 edited.setDatasetSequence(seqs[0].getDatasetSequence());
313 e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
314 { edited }, 2, 1, al);
316 assertEquals(1, testee.getSize());
317 assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
318 assertEquals(2, testee.getEdit(0).getPosition());
319 assertEquals(3, testee.getEdit(0).getNumber());
322 * Add a non-contiguous edit - should not be merged.
324 e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
325 { edited }, 2, 1, al);
327 assertEquals(2, testee.getSize());
328 assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
329 assertEquals(2, testee.getEdit(1).getPosition());
330 assertEquals(1, testee.getEdit(1).getNumber());
333 * Add an Insert after the Delete - should not be merged.
335 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
336 { edited }, 1, 1, al);
338 assertEquals(3, testee.getSize());
339 assertEquals(Action.INSERT_GAP, testee.getEdit(2).getAction());
340 assertEquals(1, testee.getEdit(2).getPosition());
341 assertEquals(1, testee.getEdit(2).getNumber());
345 * Test that the addEdit command correctly handles 'remove gaps' edits for the
346 * case when they appear contiguous but are acting on different sequences.
347 * They should not be merged.
350 public void testAddEdit_removeAllGaps()
352 seqs[0].setSequence("a???bcdefghjk");
353 Edit e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
354 { seqs[0] }, 4, 1, al);
357 seqs[1].setSequence("f??ghjklmnopq");
358 Edit e2 = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
359 { seqs[1] }, 3, 1, al);
361 assertEquals(2, testee.getSize());
362 assertSame(e, testee.getEdit(0));
363 assertSame(e2, testee.getEdit(1));
367 * Test that the addEdit command correctly merges insert gap commands acting
368 * on a multi-sequence selection.
371 public void testAddEdit_groupInsertGaps()
374 * 2 insert gap in a row (aka mouse drag right), on two sequences:
376 Edit e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
377 { seqs[0], seqs[1] }, 1, 1, al);
379 SequenceI seq1edited = new Sequence("seq0", "a?bcdefghjk");
380 seq1edited.setDatasetSequence(seqs[0].getDatasetSequence());
381 SequenceI seq2edited = new Sequence("seq1", "f?ghjklmnopq");
382 seq2edited.setDatasetSequence(seqs[1].getDatasetSequence());
383 e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
384 { seq1edited, seq2edited }, 2, 1, al);
387 assertEquals(1, testee.getSize());
388 assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
389 assertEquals(1, testee.getEdit(0).getPosition());
390 assertEquals(2, testee.getEdit(0).getNumber());
391 assertEquals(seqs[0].getDatasetSequence(), testee.getEdit(0)
392 .getSequences()[0].getDatasetSequence());
393 assertEquals(seqs[1].getDatasetSequence(), testee.getEdit(0)
394 .getSequences()[1].getDatasetSequence());
398 * Test for 'undoing' a series of gap insertions.
400 * <li>Start: ABCDEF insert 2 at pos 1</li>
401 * <li>next: A--BCDEF insert 1 at pos 4</li>
402 * <li>next: A--B-CDEF insert 2 at pos 0</li>
403 * <li>last: --A--B-CDEF</li>
407 public void testPriorState_multipleInserts()
409 EditCommand command = new EditCommand();
410 SequenceI seq = new Sequence("", "--A--B-CDEF");
411 SequenceI ds = new Sequence("", "ABCDEF");
412 seq.setDatasetSequence(ds);
413 SequenceI[] seqs = new SequenceI[]
415 Edit e = command.new Edit(Action.INSERT_GAP, seqs, 1, 2, '-');
417 e = command.new Edit(Action.INSERT_GAP, seqs, 4, 1, '-');
419 e = command.new Edit(Action.INSERT_GAP, seqs, 0, 2, '-');
422 Map<SequenceI, SequenceI> unwound = command.priorState(false);
423 assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString());
427 * Test for 'undoing' a series of gap deletions.
429 * <li>Start: A-B-C delete 1 at pos 1</li>
430 * <li>Next: AB-C delete 1 at pos 2</li>
435 public void testPriorState_removeAllGaps()
437 EditCommand command = new EditCommand();
438 SequenceI seq = new Sequence("", "ABC");
439 SequenceI ds = new Sequence("", "ABC");
440 seq.setDatasetSequence(ds);
441 SequenceI[] seqs = new SequenceI[]
443 Edit e = command.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
445 e = command.new Edit(Action.DELETE_GAP, seqs, 2, 1, '-');
448 Map<SequenceI, SequenceI> unwound = command.priorState(false);
449 assertEquals("A-B-C", unwound.get(ds).getSequenceAsString());
453 * Test for 'undoing' a single delete edit.
456 public void testPriorState_singleDelete()
458 EditCommand command = new EditCommand();
459 SequenceI seq = new Sequence("", "ABCDEF");
460 SequenceI ds = new Sequence("", "ABCDEF");
461 seq.setDatasetSequence(ds);
462 SequenceI[] seqs = new SequenceI[]
464 Edit e = command.new Edit(Action.DELETE_GAP, seqs, 2, 2, '-');
467 Map<SequenceI, SequenceI> unwound = command.priorState(false);
468 assertEquals("AB--CDEF", unwound.get(ds).getSequenceAsString());
472 * Test 'undoing' a single gap insertion edit command.
475 public void testPriorState_singleInsert()
477 EditCommand command = new EditCommand();
478 SequenceI seq = new Sequence("", "AB---CDEF");
479 SequenceI ds = new Sequence("", "ABCDEF");
480 seq.setDatasetSequence(ds);
481 SequenceI[] seqs = new SequenceI[]
483 Edit e = command.new Edit(Action.INSERT_GAP, seqs, 2, 3, '-');
486 Map<SequenceI, SequenceI> unwound = command.priorState(false);
487 assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString());
491 * Test that mimics 'remove all gaps' action. This generates delete gap edits
492 * for contiguous gaps in each sequence separately.
495 public void testPriorState_removeGapsMultipleSeqs()
497 EditCommand command = new EditCommand();
498 String original1 = "--ABC-DEF";
499 String original2 = "FG-HI--J";
500 String original3 = "M-NOPQ";
503 * Two edits for the first sequence
505 SequenceI seq = new Sequence("", "ABC-DEF");
506 SequenceI ds1 = new Sequence("", "ABCDEF");
507 seq.setDatasetSequence(ds1);
508 SequenceI[] seqs = new SequenceI[]
510 Edit e = command.new Edit(Action.DELETE_GAP, seqs, 0, 2, '-');
512 seq = new Sequence("", "ABCDEF");
513 seq.setDatasetSequence(ds1);
514 seqs = new SequenceI[]
516 e = command.new Edit(Action.DELETE_GAP, seqs, 3, 1, '-');
520 * Two edits for the second sequence
522 seq = new Sequence("", "FGHI--J");
523 SequenceI ds2 = new Sequence("", "FGHIJ");
524 seq.setDatasetSequence(ds2);
525 seqs = new SequenceI[]
527 e = command.new Edit(Action.DELETE_GAP, seqs, 2, 1, '-');
529 seq = new Sequence("", "FGHIJ");
530 seq.setDatasetSequence(ds2);
531 seqs = new SequenceI[]
533 e = command.new Edit(Action.DELETE_GAP, seqs, 4, 2, '-');
537 * One edit for the third sequence.
539 seq = new Sequence("", "MNOPQ");
540 SequenceI ds3 = new Sequence("", "MNOPQ");
541 seq.setDatasetSequence(ds3);
542 seqs = new SequenceI[]
544 e = command.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
547 Map<SequenceI, SequenceI> unwound = command.priorState(false);
548 assertEquals(original1, unwound.get(ds1).getSequenceAsString());
549 assertEquals(original2, unwound.get(ds2).getSequenceAsString());
550 assertEquals(original3, unwound.get(ds3).getSequenceAsString());
554 * Test that mimics 'remove all gapped columns' action. This generates a
555 * series Delete Gap edits that each act on all sequences that share a gapped
559 public void testPriorState_removeGappedCols()
561 EditCommand command = new EditCommand();
562 String original1 = "--ABC--DEF";
563 String original2 = "-G-HI--J";
564 String original3 = "-M-NO--PQ";
567 * First edit deletes the first column.
569 SequenceI seq1 = new Sequence("", "-ABC--DEF");
570 SequenceI ds1 = new Sequence("", "ABCDEF");
571 seq1.setDatasetSequence(ds1);
572 SequenceI seq2 = new Sequence("", "G-HI--J");
573 SequenceI ds2 = new Sequence("", "GHIJ");
574 seq2.setDatasetSequence(ds2);
575 SequenceI seq3 = new Sequence("", "M-NO--PQ");
576 SequenceI ds3 = new Sequence("", "MNOPQ");
577 seq3.setDatasetSequence(ds3);
578 SequenceI[] seqs = new SequenceI[]
579 { seq1, seq2, seq3 };
580 Edit e = command.new Edit(Action.DELETE_GAP, seqs, 0, 1, '-');
584 * Second edit deletes what is now columns 4 and 5.
586 seq1 = new Sequence("", "-ABCDEF");
587 seq1.setDatasetSequence(ds1);
588 seq2 = new Sequence("", "G-HIJ");
589 seq2.setDatasetSequence(ds2);
590 seq3 = new Sequence("", "M-NOPQ");
591 seq3.setDatasetSequence(ds3);
592 seqs = new SequenceI[]
593 { seq1, seq2, seq3 };
594 e = command.new Edit(Action.DELETE_GAP, seqs, 4, 2, '-');
597 Map<SequenceI, SequenceI> unwound = command.priorState(false);
598 assertEquals(original1, unwound.get(ds1).getSequenceAsString());
599 assertEquals(original2, unwound.get(ds2).getSequenceAsString());
600 assertEquals(original3, unwound.get(ds3).getSequenceAsString());
601 assertEquals(ds1, unwound.get(ds1).getDatasetSequence());
602 assertEquals(ds2, unwound.get(ds2).getDatasetSequence());
603 assertEquals(ds3, unwound.get(ds3).getDatasetSequence());