Clover icon

Coverage Report

  1. Project Clover database Mon Nov 11 2024 15:14:12 GMT
  2. Package jalview.commands

File EditCommandTest.java

 

Code metrics

36
508
35
1
1,249
795
54
0.11
14.51
35
1.54

Classes

Class Line # Actions
EditCommandTest 58 508 54
0.9706390597.1%
 

Contributing tests

This file is covered by 26 tests. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
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.
11    *
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.
16    *
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.
20    */
21    package jalview.commands;
22   
23    import java.util.Locale;
24   
25    import static org.testng.AssertJUnit.assertEquals;
26    import static org.testng.AssertJUnit.assertSame;
27    import static org.testng.AssertJUnit.assertTrue;
28   
29    import jalview.commands.EditCommand.Action;
30    import jalview.commands.EditCommand.Edit;
31    import jalview.datamodel.Alignment;
32    import jalview.datamodel.AlignmentI;
33    import jalview.datamodel.Sequence;
34    import jalview.datamodel.SequenceFeature;
35    import jalview.datamodel.SequenceI;
36    import jalview.datamodel.features.SequenceFeatures;
37    import jalview.gui.JvOptionPane;
38   
39    import java.nio.charset.Charset;
40    import java.util.Arrays;
41    import java.util.Collections;
42    import java.util.Comparator;
43    import java.util.List;
44    import java.util.Map;
45    import java.util.spi.LocaleServiceProvider;
46   
47    import org.testng.Assert;
48    import org.testng.annotations.BeforeClass;
49    import org.testng.annotations.BeforeMethod;
50    import org.testng.annotations.Test;
51   
52    /**
53    * Unit tests for EditCommand
54    *
55    * @author gmcarstairs
56    *
57    */
 
58    public class EditCommandTest
59    {
60    private static Comparator<SequenceFeature> BY_DESCRIPTION = new Comparator<SequenceFeature>()
61    {
62   
 
63  1144 toggle @Override
64    public int compare(SequenceFeature o1, SequenceFeature o2)
65    {
66  1144 return o1.getDescription().compareTo(o2.getDescription());
67    }
68    };
69   
70    private EditCommand testee;
71   
72    private SequenceI[] seqs;
73   
74    private Alignment al;
75   
76    /*
77    * compute n(n+1)/2 e.g.
78    * func(5) = 5 + 4 + 3 + 2 + 1 = 15
79    */
 
80  58 toggle private static int func(int i)
81    {
82  58 return i * (i + 1) / 2;
83    }
84   
 
85  1 toggle @BeforeClass(alwaysRun = true)
86    public void setUpJvOptionPane()
87    {
88  1 JvOptionPane.setInteractiveMode(false);
89  1 JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
90    }
91   
 
92  26 toggle @BeforeMethod(alwaysRun = true)
93    public void setUp()
94    {
95  26 testee = new EditCommand();
96  26 seqs = new SequenceI[4];
97  26 seqs[0] = new Sequence("seq0", "abcdefghjk");
98  26 seqs[0].setDatasetSequence(new Sequence("seq0ds", "ABCDEFGHJK"));
99  26 seqs[1] = new Sequence("seq1", "fghjklmnopq");
100  26 seqs[1].setDatasetSequence(new Sequence("seq1ds", "FGHJKLMNOPQ"));
101  26 seqs[2] = new Sequence("seq2", "qrstuvwxyz");
102  26 seqs[2].setDatasetSequence(new Sequence("seq2ds", "QRSTUVWXYZ"));
103  26 seqs[3] = new Sequence("seq3", "1234567890");
104  26 seqs[3].setDatasetSequence(new Sequence("seq3ds", "1234567890"));
105  26 al = new Alignment(seqs);
106  26 al.setGapCharacter('?');
107    }
108   
109    /**
110    * Test inserting gap characters
111    */
 
112  1 toggle @Test(groups = { "Functional" })
113    public void testAppendEdit_insertGap()
114    {
115    // set a non-standard gap character to prove it is actually used
116  1 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 3, al, true);
117  1 assertEquals("abcd???efghjk", seqs[0].getSequenceAsString());
118  1 assertEquals("fghj???klmnopq", seqs[1].getSequenceAsString());
119  1 assertEquals("qrst???uvwxyz", seqs[2].getSequenceAsString());
120  1 assertEquals("1234???567890", seqs[3].getSequenceAsString());
121   
122    // todo: test for handling out of range positions?
123    }
124   
125    /**
126    * Test deleting characters from sequences. Note the deleteGap() action does
127    * not check that only gap characters are being removed.
128    */
 
129  1 toggle @Test(groups = { "Functional" })
130    public void testAppendEdit_deleteGap()
131    {
132  1 testee.appendEdit(Action.DELETE_GAP, seqs, 4, 3, al, true);
133  1 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
134  1 assertEquals("fghjnopq", seqs[1].getSequenceAsString());
135  1 assertEquals("qrstxyz", seqs[2].getSequenceAsString());
136  1 assertEquals("1234890", seqs[3].getSequenceAsString());
137    }
138   
139    /**
140    * Test a cut action. The command should store the cut characters to support
141    * undo.
142    */
 
143  1 toggle @Test(groups = { "Functional" })
144    public void testCut()
145    {
146  1 Edit ec = testee.new Edit(Action.CUT, seqs, 4, 3, al);
147  1 EditCommand.cut(ec, new AlignmentI[] { al });
148  1 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
149  1 assertEquals("fghjnopq", seqs[1].getSequenceAsString());
150  1 assertEquals("qrstxyz", seqs[2].getSequenceAsString());
151  1 assertEquals("1234890", seqs[3].getSequenceAsString());
152   
153  1 assertEquals("efg", new String(ec.string[0]));
154  1 assertEquals("klm", new String(ec.string[1]));
155  1 assertEquals("uvw", new String(ec.string[2]));
156  1 assertEquals("567", new String(ec.string[3]));
157    // TODO: case where whole sequence is deleted as nothing left; etc
158    }
159   
160    /**
161    * Test a Paste action, followed by Undo and Redo
162    */
 
163  0 toggle @Test(groups = { "Functional" }, enabled = false)
164    public void testPaste_undo_redo()
165    {
166    // TODO code this test properly, bearing in mind that:
167    // Paste action requires something on the clipboard (Cut/Copy)
168    // - EditCommand.paste doesn't add sequences to the alignment
169    // ... that is done in AlignFrame.paste()
170    // ... unless as a Redo
171    // ...
172   
173  0 SequenceI[] newSeqs = new SequenceI[2];
174  0 newSeqs[0] = new Sequence("newseq0", "ACEFKL");
175  0 newSeqs[1] = new Sequence("newseq1", "JWMPDH");
176   
177  0 new EditCommand("Paste", Action.PASTE, newSeqs, 0, al.getWidth(), al);
178  0 assertEquals(6, al.getSequences().size());
179  0 assertEquals("1234567890", seqs[3].getSequenceAsString());
180  0 assertEquals("ACEFKL", seqs[4].getSequenceAsString());
181  0 assertEquals("JWMPDH", seqs[5].getSequenceAsString());
182    }
183   
184    /**
185    * Test insertGap followed by undo command
186    */
 
187  1 toggle @Test(groups = { "Functional" })
188    public void testUndo_insertGap()
189    {
190    // Edit ec = testee.new Edit(Action.INSERT_GAP, seqs, 4, 3, '?');
191  1 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 3, al, true);
192    // check something changed
193  1 assertEquals("abcd???efghjk", seqs[0].getSequenceAsString());
194  1 testee.undoCommand(new AlignmentI[] { al });
195  1 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
196  1 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
197  1 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
198  1 assertEquals("1234567890", seqs[3].getSequenceAsString());
199    }
200   
201    /**
202    * Test deleteGap followed by undo command
203    */
 
204  1 toggle @Test(groups = { "Functional" })
205    public void testUndo_deleteGap()
206    {
207  1 testee.appendEdit(Action.DELETE_GAP, seqs, 4, 3, al, true);
208    // check something changed
209  1 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
210  1 testee.undoCommand(new AlignmentI[] { al });
211    // deleteGap doesn't 'remember' deleted characters, only gaps get put back
212  1 assertEquals("abcd???hjk", seqs[0].getSequenceAsString());
213  1 assertEquals("fghj???nopq", seqs[1].getSequenceAsString());
214  1 assertEquals("qrst???xyz", seqs[2].getSequenceAsString());
215  1 assertEquals("1234???890", seqs[3].getSequenceAsString());
216    }
217   
218    /**
219    * Test several commands followed by an undo command
220    */
 
221  1 toggle @Test(groups = { "Functional" })
222    public void testUndo_multipleCommands()
223    {
224    // delete positions 3/4/5 (counting from 1)
225  1 testee.appendEdit(Action.DELETE_GAP, seqs, 2, 3, al, true);
226  1 assertEquals("abfghjk", seqs[0].getSequenceAsString());
227  1 assertEquals("1267890", seqs[3].getSequenceAsString());
228   
229    // insert 2 gaps after the second residue
230  1 testee.appendEdit(Action.INSERT_GAP, seqs, 2, 2, al, true);
231  1 assertEquals("ab??fghjk", seqs[0].getSequenceAsString());
232  1 assertEquals("12??67890", seqs[3].getSequenceAsString());
233   
234    // delete positions 4/5/6
235  1 testee.appendEdit(Action.DELETE_GAP, seqs, 3, 3, al, true);
236  1 assertEquals("ab?hjk", seqs[0].getSequenceAsString());
237  1 assertEquals("12?890", seqs[3].getSequenceAsString());
238   
239    // undo edit commands
240  1 testee.undoCommand(new AlignmentI[] { al });
241  1 assertEquals("ab?????hjk", seqs[0].getSequenceAsString());
242  1 assertEquals("12?????890", seqs[3].getSequenceAsString());
243    }
244   
245    /**
246    * Unit test for JAL-1594 bug: click and drag sequence right to insert gaps -
247    * undo did not remove them all.
248    */
 
249  1 toggle @Test(groups = { "Functional" })
250    public void testUndo_multipleInsertGaps()
251    {
252  1 testee.appendEdit(Action.INSERT_GAP, seqs, 4, 1, al, true);
253  1 testee.appendEdit(Action.INSERT_GAP, seqs, 5, 1, al, true);
254  1 testee.appendEdit(Action.INSERT_GAP, seqs, 6, 1, al, true);
255   
256    // undo edit commands
257  1 testee.undoCommand(new AlignmentI[] { al });
258  1 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
259  1 assertEquals("1234567890", seqs[3].getSequenceAsString());
260   
261    }
262   
263    /**
264    * Test cut followed by undo command
265    */
 
266  1 toggle @Test(groups = { "Functional" })
267    public void testUndo_cut()
268    {
269  1 testee.appendEdit(Action.CUT, seqs, 4, 3, al, true);
270    // check something changed
271  1 assertEquals("abcdhjk", seqs[0].getSequenceAsString());
272  1 testee.undoCommand(new AlignmentI[] { al });
273  1 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
274  1 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
275  1 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
276  1 assertEquals("1234567890", seqs[3].getSequenceAsString());
277    }
278   
279    /**
280    * Test the replace command (used to manually edit a sequence)
281    */
 
282  1 toggle @Test(groups = { "Functional" })
283    public void testReplace()
284    {
285    // seem to need a dataset sequence on the edited sequence here
286  1 seqs[1].createDatasetSequence();
287  1 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
288    // NB command.number holds end position for a Replace command
289  1 new EditCommand("", Action.REPLACE, "Z-xY", new SequenceI[] { seqs[1] },
290    4, 8, al);
291  1 assertEquals("abcdefghjk", seqs[0].getSequenceAsString());
292  1 assertEquals("fghjZ-xYopq", seqs[1].getSequenceAsString());
293    // Dataset Sequence should always be uppercase
294  1 assertEquals("fghjZxYopq".toUpperCase(Locale.ROOT),
295    seqs[1].getDatasetSequence().getSequenceAsString());
296  1 assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
297  1 assertEquals("1234567890", seqs[3].getSequenceAsString());
298    }
299   
300    /**
301    * Test the replace command (used to manually edit a sequence)
302    */
 
303  1 toggle @Test(groups = { "Functional" })
304    public void testReplace_withGaps()
305    {
306  1 SequenceI seq = new Sequence("seq", "ABC--DEF");
307  1 seq.createDatasetSequence();
308  1 assertEquals("ABCDEF", seq.getDatasetSequence().getSequenceAsString());
309  1 assertEquals(1, seq.getStart());
310  1 assertEquals(6, seq.getEnd());
311   
312    /*
313    * replace C- with XYZ
314    * NB arg4 = start column of selection for edit (base 0)
315    * arg5 = column after end of selection for edit
316    */
317  1 EditCommand edit = new EditCommand("", Action.REPLACE, "xyZ",
318    new SequenceI[]
319    { seq }, 2, 4, al);
320  1 assertEquals("ABxyZ-DEF", seq.getSequenceAsString());
321  1 assertEquals(1, seq.getStart());
322  1 assertEquals(8, seq.getEnd());
323    // Dataset sequence always uppercase
324  1 assertEquals("ABxyZDEF".toUpperCase(Locale.ROOT),
325    seq.getDatasetSequence().getSequenceAsString());
326  1 assertEquals(8, seq.getDatasetSequence().getEnd());
327   
328    /*
329    * undo the edit
330    */
331  1 AlignmentI[] views = new AlignmentI[] {
332    new Alignment(new SequenceI[]
333    { seq }) };
334  1 edit.undoCommand(views);
335   
336  1 assertEquals("ABC--DEF", seq.getSequenceAsString());
337  1 assertEquals("ABCDEF", seq.getDatasetSequence().getSequenceAsString());
338  1 assertEquals(1, seq.getStart());
339  1 assertEquals(6, seq.getEnd());
340  1 assertEquals(6, seq.getDatasetSequence().getEnd());
341   
342    /*
343    * redo the edit
344    */
345  1 edit.doCommand(views);
346   
347  1 assertEquals("ABxyZ-DEF", seq.getSequenceAsString());
348  1 assertEquals(1, seq.getStart());
349  1 assertEquals(8, seq.getEnd());
350    // dataset sequence should be Uppercase
351  1 assertEquals("ABxyZDEF".toUpperCase(Locale.ROOT),
352    seq.getDatasetSequence().getSequenceAsString());
353  1 assertEquals(8, seq.getDatasetSequence().getEnd());
354   
355    }
356   
357    /**
358    * Test replace command when it doesn't cause a sequence edit (see comment in
359    */
 
360  1 toggle @Test(groups = { "Functional" })
361    public void testReplaceFirstResiduesWithGaps()
362    {
363    // test replace when gaps are inserted at start. Start/end should change
364    // w.r.t. original edited sequence.
365  1 SequenceI dsseq = seqs[1].getDatasetSequence();
366  1 EditCommand edit = new EditCommand("", Action.REPLACE, "----",
367    new SequenceI[]
368    { seqs[1] }, 0, 4, al);
369   
370    // trimmed start
371  1 assertEquals("----klmnopq", seqs[1].getSequenceAsString());
372    // and ds is preserved
373  1 assertTrue(dsseq == seqs[1].getDatasetSequence());
374    // and it is unchanged and UPPERCASE !
375  1 assertEquals("fghjklmnopq".toUpperCase(Locale.ROOT),
376    dsseq.getSequenceAsString());
377    // and that alignment sequence start has been adjusted
378  1 assertEquals(5, seqs[1].getStart());
379  1 assertEquals(11, seqs[1].getEnd());
380   
381  1 AlignmentI[] views = new AlignmentI[] { new Alignment(seqs) };
382    // and undo
383  1 edit.undoCommand(views);
384   
385    // dataset sequence unchanged
386  1 assertTrue(dsseq == seqs[1].getDatasetSequence());
387    // restore sequence
388  1 assertEquals("fghjklmnopq", seqs[1].getSequenceAsString());
389    // and start/end numbering also restored
390  1 assertEquals(1, seqs[1].getStart());
391  1 assertEquals(11, seqs[1].getEnd());
392   
393    // now redo
394  1 edit.undoCommand(views);
395   
396    // and repeat asserts for the original edit
397   
398    // trimmed start
399  1 assertEquals("----klmnopq", seqs[1].getSequenceAsString());
400    // and ds is preserved
401  1 assertTrue(dsseq == seqs[1].getDatasetSequence());
402    // and it is unchanged AND UPPERCASE !
403  1 assertEquals("fghjklmnopq".toUpperCase(Locale.ROOT),
404    dsseq.getSequenceAsString());
405    // and that alignment sequence start has been adjusted
406  1 assertEquals(5, seqs[1].getStart());
407  1 assertEquals(11, seqs[1].getEnd());
408   
409    }
410   
411    /**
412    * Test that the addEdit command correctly merges insert gap commands when
413    * possible.
414    */
 
415  1 toggle @Test(groups = { "Functional" })
416    public void testAddEdit_multipleInsertGap()
417    {
418    /*
419    * 3 insert gap in a row (aka mouse drag right):
420    */
421  1 Edit e = new EditCommand().new Edit(Action.INSERT_GAP,
422    new SequenceI[]
423    { seqs[0] }, 1, 1, al);
424  1 testee.addEdit(e);
425  1 SequenceI edited = new Sequence("seq0", "a?bcdefghjk");
426  1 edited.setDatasetSequence(seqs[0].getDatasetSequence());
427  1 e = new EditCommand().new Edit(Action.INSERT_GAP,
428    new SequenceI[]
429    { edited }, 2, 1, al);
430  1 testee.addEdit(e);
431  1 edited = new Sequence("seq0", "a??bcdefghjk");
432  1 edited.setDatasetSequence(seqs[0].getDatasetSequence());
433  1 e = new EditCommand().new Edit(Action.INSERT_GAP,
434    new SequenceI[]
435    { edited }, 3, 1, al);
436  1 testee.addEdit(e);
437  1 assertEquals(1, testee.getSize());
438  1 assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
439  1 assertEquals(1, testee.getEdit(0).getPosition());
440  1 assertEquals(3, testee.getEdit(0).getNumber());
441   
442    /*
443    * Add a non-contiguous edit - should not be merged.
444    */
445  1 e = new EditCommand().new Edit(Action.INSERT_GAP,
446    new SequenceI[]
447    { edited }, 5, 2, al);
448  1 testee.addEdit(e);
449  1 assertEquals(2, testee.getSize());
450  1 assertEquals(5, testee.getEdit(1).getPosition());
451  1 assertEquals(2, testee.getEdit(1).getNumber());
452   
453    /*
454    * Add a Delete after the Insert - should not be merged.
455    */
456  1 e = new EditCommand().new Edit(Action.DELETE_GAP,
457    new SequenceI[]
458    { edited }, 6, 2, al);
459  1 testee.addEdit(e);
460  1 assertEquals(3, testee.getSize());
461  1 assertEquals(Action.DELETE_GAP, testee.getEdit(2).getAction());
462  1 assertEquals(6, testee.getEdit(2).getPosition());
463  1 assertEquals(2, testee.getEdit(2).getNumber());
464    }
465   
466    /**
467    * Test that the addEdit command correctly merges delete gap commands when
468    * possible.
469    */
 
470  1 toggle @Test(groups = { "Functional" })
471    public void testAddEdit_multipleDeleteGap()
472    {
473    /*
474    * 3 delete gap in a row (aka mouse drag left):
475    */
476  1 seqs[0].setSequence("a???bcdefghjk");
477  1 Edit e = new EditCommand().new Edit(Action.DELETE_GAP,
478    new SequenceI[]
479    { seqs[0] }, 4, 1, al);
480  1 testee.addEdit(e);
481  1 assertEquals(1, testee.getSize());
482   
483  1 SequenceI edited = new Sequence("seq0", "a??bcdefghjk");
484  1 edited.setDatasetSequence(seqs[0].getDatasetSequence());
485  1 e = new EditCommand().new Edit(Action.DELETE_GAP,
486    new SequenceI[]
487    { edited }, 3, 1, al);
488  1 testee.addEdit(e);
489  1 assertEquals(1, testee.getSize());
490   
491  1 edited = new Sequence("seq0", "a?bcdefghjk");
492  1 edited.setDatasetSequence(seqs[0].getDatasetSequence());
493  1 e = new EditCommand().new Edit(Action.DELETE_GAP,
494    new SequenceI[]
495    { edited }, 2, 1, al);
496  1 testee.addEdit(e);
497  1 assertEquals(1, testee.getSize());
498  1 assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
499  1 assertEquals(2, testee.getEdit(0).getPosition());
500  1 assertEquals(3, testee.getEdit(0).getNumber());
501   
502    /*
503    * Add a non-contiguous edit - should not be merged.
504    */
505  1 e = new EditCommand().new Edit(Action.DELETE_GAP,
506    new SequenceI[]
507    { edited }, 2, 1, al);
508  1 testee.addEdit(e);
509  1 assertEquals(2, testee.getSize());
510  1 assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
511  1 assertEquals(2, testee.getEdit(1).getPosition());
512  1 assertEquals(1, testee.getEdit(1).getNumber());
513   
514    /*
515    * Add an Insert after the Delete - should not be merged.
516    */
517  1 e = new EditCommand().new Edit(Action.INSERT_GAP,
518    new SequenceI[]
519    { edited }, 1, 1, al);
520  1 testee.addEdit(e);
521  1 assertEquals(3, testee.getSize());
522  1 assertEquals(Action.INSERT_GAP, testee.getEdit(2).getAction());
523  1 assertEquals(1, testee.getEdit(2).getPosition());
524  1 assertEquals(1, testee.getEdit(2).getNumber());
525    }
526   
527    /**
528    * Test that the addEdit command correctly handles 'remove gaps' edits for the
529    * case when they appear contiguous but are acting on different sequences.
530    * They should not be merged.
531    */
 
532  1 toggle @Test(groups = { "Functional" })
533    public void testAddEdit_removeAllGaps()
534    {
535  1 seqs[0].setSequence("a???bcdefghjk");
536  1 Edit e = new EditCommand().new Edit(Action.DELETE_GAP,
537    new SequenceI[]
538    { seqs[0] }, 4, 1, al);
539  1 testee.addEdit(e);
540   
541  1 seqs[1].setSequence("f??ghjklmnopq");
542  1 Edit e2 = new EditCommand().new Edit(Action.DELETE_GAP,
543    new SequenceI[]
544    { seqs[1] }, 3, 1, al);
545  1 testee.addEdit(e2);
546  1 assertEquals(2, testee.getSize());
547  1 assertSame(e, testee.getEdit(0));
548  1 assertSame(e2, testee.getEdit(1));
549    }
550   
551    /**
552    * Test that the addEdit command correctly merges insert gap commands acting
553    * on a multi-sequence selection.
554    */
 
555  1 toggle @Test(groups = { "Functional" })
556    public void testAddEdit_groupInsertGaps()
557    {
558    /*
559    * 2 insert gap in a row (aka mouse drag right), on two sequences:
560    */
561  1 Edit e = new EditCommand().new Edit(Action.INSERT_GAP,
562    new SequenceI[]
563    { seqs[0], seqs[1] }, 1, 1, al);
564  1 testee.addEdit(e);
565  1 SequenceI seq1edited = new Sequence("seq0", "a?bcdefghjk");
566  1 seq1edited.setDatasetSequence(seqs[0].getDatasetSequence());
567  1 SequenceI seq2edited = new Sequence("seq1", "f?ghjklmnopq");
568  1 seq2edited.setDatasetSequence(seqs[1].getDatasetSequence());
569  1 e = new EditCommand().new Edit(Action.INSERT_GAP,
570    new SequenceI[]
571    { seq1edited, seq2edited }, 2, 1, al);
572  1 testee.addEdit(e);
573   
574  1 assertEquals(1, testee.getSize());
575  1 assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
576  1 assertEquals(1, testee.getEdit(0).getPosition());
577  1 assertEquals(2, testee.getEdit(0).getNumber());
578  1 assertEquals(seqs[0].getDatasetSequence(),
579    testee.getEdit(0).getSequences()[0].getDatasetSequence());
580  1 assertEquals(seqs[1].getDatasetSequence(),
581    testee.getEdit(0).getSequences()[1].getDatasetSequence());
582    }
583   
584    /**
585    * Test for 'undoing' a series of gap insertions.
586    * <ul>
587    * <li>Start: ABCDEF insert 2 at pos 1</li>
588    * <li>next: A--BCDEF insert 1 at pos 4</li>
589    * <li>next: A--B-CDEF insert 2 at pos 0</li>
590    * <li>last: --A--B-CDEF</li>
591    * </ul>
592    */
 
593  1 toggle @Test(groups = { "Functional" })
594    public void testPriorState_multipleInserts()
595    {
596  1 EditCommand command = new EditCommand();
597  1 SequenceI seq = new Sequence("", "--A--B-CDEF");
598  1 SequenceI ds = new Sequence("", "ABCDEF");
599  1 seq.setDatasetSequence(ds);
600  1 SequenceI[] sqs = new SequenceI[] { seq };
601  1 Edit e = command.new Edit(Action.INSERT_GAP, sqs, 1, 2, '-');
602  1 command.addEdit(e);
603  1 e = command.new Edit(Action.INSERT_GAP, sqs, 4, 1, '-');
604  1 command.addEdit(e);
605  1 e = command.new Edit(Action.INSERT_GAP, sqs, 0, 2, '-');
606  1 command.addEdit(e);
607   
608  1 Map<SequenceI, SequenceI> unwound = command.priorState(false);
609  1 assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString());
610    }
611   
612    /**
613    * Test for 'undoing' a series of gap deletions.
614    * <ul>
615    * <li>Start: A-B-C delete 1 at pos 1</li>
616    * <li>Next: AB-C delete 1 at pos 2</li>
617    * <li>End: ABC</li>
618    * </ul>
619    */
 
620  1 toggle @Test(groups = { "Functional" })
621    public void testPriorState_removeAllGaps()
622    {
623  1 EditCommand command = new EditCommand();
624  1 SequenceI seq = new Sequence("", "ABC");
625  1 SequenceI ds = new Sequence("", "ABC");
626  1 seq.setDatasetSequence(ds);
627  1 SequenceI[] sqs = new SequenceI[] { seq };
628  1 Edit e = command.new Edit(Action.DELETE_GAP, sqs, 1, 1, '-');
629  1 command.addEdit(e);
630  1 e = command.new Edit(Action.DELETE_GAP, sqs, 2, 1, '-');
631  1 command.addEdit(e);
632   
633  1 Map<SequenceI, SequenceI> unwound = command.priorState(false);
634  1 assertEquals("A-B-C", unwound.get(ds).getSequenceAsString());
635    }
636   
637    /**
638    * Test for 'undoing' a single delete edit.
639    */
 
640  1 toggle @Test(groups = { "Functional" })
641    public void testPriorState_singleDelete()
642    {
643  1 EditCommand command = new EditCommand();
644  1 SequenceI seq = new Sequence("", "ABCDEF");
645  1 SequenceI ds = new Sequence("", "ABCDEF");
646  1 seq.setDatasetSequence(ds);
647  1 SequenceI[] sqs = new SequenceI[] { seq };
648  1 Edit e = command.new Edit(Action.DELETE_GAP, sqs, 2, 2, '-');
649  1 command.addEdit(e);
650   
651  1 Map<SequenceI, SequenceI> unwound = command.priorState(false);
652  1 assertEquals("AB--CDEF", unwound.get(ds).getSequenceAsString());
653    }
654   
655    /**
656    * Test 'undoing' a single gap insertion edit command.
657    */
 
658  1 toggle @Test(groups = { "Functional" })
659    public void testPriorState_singleInsert()
660    {
661  1 EditCommand command = new EditCommand();
662  1 SequenceI seq = new Sequence("", "AB---CDEF");
663  1 SequenceI ds = new Sequence("", "ABCDEF");
664  1 seq.setDatasetSequence(ds);
665  1 SequenceI[] sqs = new SequenceI[] { seq };
666  1 Edit e = command.new Edit(Action.INSERT_GAP, sqs, 2, 3, '-');
667  1 command.addEdit(e);
668   
669  1 Map<SequenceI, SequenceI> unwound = command.priorState(false);
670  1 SequenceI prior = unwound.get(ds);
671  1 assertEquals("ABCDEF", prior.getSequenceAsString());
672  1 assertEquals(1, prior.getStart());
673  1 assertEquals(6, prior.getEnd());
674    }
675   
676    /**
677    * Test 'undoing' a single gap insertion edit command, on a sequence whose
678    * start residue is other than 1
679    */
 
680  1 toggle @Test(groups = { "Functional" })
681    public void testPriorState_singleInsertWithOffset()
682    {
683  1 EditCommand command = new EditCommand();
684  1 SequenceI seq = new Sequence("", "AB---CDEF", 8, 13);
685    // SequenceI ds = new Sequence("", "ABCDEF", 8, 13);
686    // seq.setDatasetSequence(ds);
687  1 seq.createDatasetSequence();
688  1 SequenceI[] sqs = new SequenceI[] { seq };
689  1 Edit e = command.new Edit(Action.INSERT_GAP, sqs, 2, 3, '-');
690  1 command.addEdit(e);
691   
692  1 Map<SequenceI, SequenceI> unwound = command.priorState(false);
693  1 SequenceI prior = unwound.get(seq.getDatasetSequence());
694  1 assertEquals("ABCDEF", prior.getSequenceAsString());
695  1 assertEquals(8, prior.getStart());
696  1 assertEquals(13, prior.getEnd());
697    }
698   
699    /**
700    * Test that mimics 'remove all gaps' action. This generates delete gap edits
701    * for contiguous gaps in each sequence separately.
702    */
 
703  1 toggle @Test(groups = { "Functional" })
704    public void testPriorState_removeGapsMultipleSeqs()
705    {
706  1 EditCommand command = new EditCommand();
707  1 String original1 = "--ABC-DEF";
708  1 String original2 = "FG-HI--J";
709  1 String original3 = "M-NOPQ";
710   
711    /*
712    * Two edits for the first sequence
713    */
714  1 SequenceI seq = new Sequence("", "ABC-DEF");
715  1 SequenceI ds1 = new Sequence("", "ABCDEF");
716  1 seq.setDatasetSequence(ds1);
717  1 SequenceI[] sqs = new SequenceI[] { seq };
718  1 Edit e = command.new Edit(Action.DELETE_GAP, sqs, 0, 2, '-');
719  1 command.addEdit(e);
720  1 seq = new Sequence("", "ABCDEF");
721  1 seq.setDatasetSequence(ds1);
722  1 sqs = new SequenceI[] { seq };
723  1 e = command.new Edit(Action.DELETE_GAP, sqs, 3, 1, '-');
724  1 command.addEdit(e);
725   
726    /*
727    * Two edits for the second sequence
728    */
729  1 seq = new Sequence("", "FGHI--J");
730  1 SequenceI ds2 = new Sequence("", "FGHIJ");
731  1 seq.setDatasetSequence(ds2);
732  1 sqs = new SequenceI[] { seq };
733  1 e = command.new Edit(Action.DELETE_GAP, sqs, 2, 1, '-');
734  1 command.addEdit(e);
735  1 seq = new Sequence("", "FGHIJ");
736  1 seq.setDatasetSequence(ds2);
737  1 sqs = new SequenceI[] { seq };
738  1 e = command.new Edit(Action.DELETE_GAP, sqs, 4, 2, '-');
739  1 command.addEdit(e);
740   
741    /*
742    * One edit for the third sequence.
743    */
744  1 seq = new Sequence("", "MNOPQ");
745  1 SequenceI ds3 = new Sequence("", "MNOPQ");
746  1 seq.setDatasetSequence(ds3);
747  1 sqs = new SequenceI[] { seq };
748  1 e = command.new Edit(Action.DELETE_GAP, sqs, 1, 1, '-');
749  1 command.addEdit(e);
750   
751  1 Map<SequenceI, SequenceI> unwound = command.priorState(false);
752  1 assertEquals(original1, unwound.get(ds1).getSequenceAsString());
753  1 assertEquals(original2, unwound.get(ds2).getSequenceAsString());
754  1 assertEquals(original3, unwound.get(ds3).getSequenceAsString());
755    }
756   
757    /**
758    * Test that mimics 'remove all gapped columns' action. This generates a
759    * series Delete Gap edits that each act on all sequences that share a gapped
760    * column region.
761    */
 
762  1 toggle @Test(groups = { "Functional" })
763    public void testPriorState_removeGappedCols()
764    {
765  1 EditCommand command = new EditCommand();
766  1 String original1 = "--ABC--DEF";
767  1 String original2 = "-G-HI--J";
768  1 String original3 = "-M-NO--PQ";
769   
770    /*
771    * First edit deletes the first column.
772    */
773  1 SequenceI seq1 = new Sequence("", "-ABC--DEF");
774  1 SequenceI ds1 = new Sequence("", "ABCDEF");
775  1 seq1.setDatasetSequence(ds1);
776  1 SequenceI seq2 = new Sequence("", "G-HI--J");
777  1 SequenceI ds2 = new Sequence("", "GHIJ");
778  1 seq2.setDatasetSequence(ds2);
779  1 SequenceI seq3 = new Sequence("", "M-NO--PQ");
780  1 SequenceI ds3 = new Sequence("", "MNOPQ");
781  1 seq3.setDatasetSequence(ds3);
782  1 SequenceI[] sqs = new SequenceI[] { seq1, seq2, seq3 };
783  1 Edit e = command.new Edit(Action.DELETE_GAP, sqs, 0, 1, '-');
784  1 command.addEdit(e);
785   
786    /*
787    * Second edit deletes what is now columns 4 and 5.
788    */
789  1 seq1 = new Sequence("", "-ABCDEF");
790  1 seq1.setDatasetSequence(ds1);
791  1 seq2 = new Sequence("", "G-HIJ");
792  1 seq2.setDatasetSequence(ds2);
793  1 seq3 = new Sequence("", "M-NOPQ");
794  1 seq3.setDatasetSequence(ds3);
795  1 sqs = new SequenceI[] { seq1, seq2, seq3 };
796  1 e = command.new Edit(Action.DELETE_GAP, sqs, 4, 2, '-');
797  1 command.addEdit(e);
798   
799  1 Map<SequenceI, SequenceI> unwound = command.priorState(false);
800  1 assertEquals(original1, unwound.get(ds1).getSequenceAsString());
801  1 assertEquals(original2, unwound.get(ds2).getSequenceAsString());
802  1 assertEquals(original3, unwound.get(ds3).getSequenceAsString());
803  1 assertEquals(ds1, unwound.get(ds1).getDatasetSequence());
804  1 assertEquals(ds2, unwound.get(ds2).getDatasetSequence());
805  1 assertEquals(ds3, unwound.get(ds3).getDatasetSequence());
806    }
807   
808    /**
809    * Test a cut action's relocation of sequence features
810    */
 
811  1 toggle @Test(groups = { "Functional" })
812    public void testCut_withFeatures()
813    {
814    /*
815    * create sequence features before, after and overlapping
816    * a cut of columns/residues 4-7
817    */
818  1 SequenceI seq0 = seqs[0]; // abcdefghjk/1-10
819  1 seq0.addSequenceFeature(
820    new SequenceFeature("before", "", 1, 3, 0f, null));
821  1 seq0.addSequenceFeature(
822    new SequenceFeature("overlap left", "", 2, 6, 0f, null));
823  1 seq0.addSequenceFeature(
824    new SequenceFeature("internal", "", 5, 6, 0f, null));
825  1 seq0.addSequenceFeature(
826    new SequenceFeature("overlap right", "", 7, 8, 0f, null));
827  1 seq0.addSequenceFeature(
828    new SequenceFeature("after", "", 8, 10, 0f, null));
829   
830    /*
831    * add some contact features
832    */
833  1 SequenceFeature internalContact = new SequenceFeature("disulphide bond",
834    "", 5, 6, 0f, null);
835  1 seq0.addSequenceFeature(internalContact); // should get deleted
836  1 SequenceFeature overlapLeftContact = new SequenceFeature(
837    "disulphide bond", "", 2, 6, 0f, null);
838  1 seq0.addSequenceFeature(overlapLeftContact); // should get deleted
839  1 SequenceFeature overlapRightContact = new SequenceFeature(
840    "disulphide bond", "", 5, 8, 0f, null);
841  1 seq0.addSequenceFeature(overlapRightContact); // should get deleted
842  1 SequenceFeature spanningContact = new SequenceFeature("disulphide bond",
843    "", 2, 9, 0f, null);
844  1 seq0.addSequenceFeature(spanningContact); // should get shortened 3'
845   
846    /*
847    * cut columns 3-6 (base 0), residues d-g 4-7
848    */
849  1 Edit ec = testee.new Edit(Action.CUT, seqs, 3, 4, al); // cols 3-6 base 0
850  1 EditCommand.cut(ec, new AlignmentI[] { al });
851   
852  1 List<SequenceFeature> sfs = seq0.getSequenceFeatures();
853  1 SequenceFeatures.sortFeatures(sfs, true);
854   
855  1 assertEquals(5, sfs.size()); // features internal to cut were deleted
856  1 SequenceFeature sf = sfs.get(0);
857  1 assertEquals("before", sf.getType());
858  1 assertEquals(1, sf.getBegin());
859  1 assertEquals(3, sf.getEnd());
860  1 sf = sfs.get(1);
861  1 assertEquals("disulphide bond", sf.getType());
862  1 assertEquals(2, sf.getBegin());
863  1 assertEquals(5, sf.getEnd()); // truncated by cut
864  1 sf = sfs.get(2);
865  1 assertEquals("overlap left", sf.getType());
866  1 assertEquals(2, sf.getBegin());
867  1 assertEquals(3, sf.getEnd()); // truncated by cut
868  1 sf = sfs.get(3);
869  1 assertEquals("after", sf.getType());
870  1 assertEquals(4, sf.getBegin()); // shifted left by cut
871  1 assertEquals(6, sf.getEnd()); // shifted left by cut
872  1 sf = sfs.get(4);
873  1 assertEquals("overlap right", sf.getType());
874  1 assertEquals(4, sf.getBegin()); // shifted left by cut
875  1 assertEquals(4, sf.getEnd()); // truncated by cut
876    }
877   
878    /**
879    * Test a cut action's relocation of sequence features, with full coverage of
880    * all possible feature and cut locations for a 5-position ungapped sequence
881    */
 
882  1 toggle @Test(groups = { "Functional" })
883    public void testCut_withFeatures_exhaustive()
884    {
885    /*
886    * create a sequence features on each subrange of 1-5
887    */
888  1 SequenceI seq0 = new Sequence("seq", "ABCDE");
889  1 int start = 8;
890  1 int end = 12;
891  1 seq0.setStart(start);
892  1 seq0.setEnd(end);
893  1 AlignmentI alignment = new Alignment(new SequenceI[] { seq0 });
894  1 alignment.setDataset(null);
895   
896    /*
897    * create a new alignment with shared dataset sequence
898    */
899  1 AlignmentI copy = new Alignment(
900    new SequenceI[]
901    { alignment.getDataset().getSequenceAt(0).deriveSequence() });
902  1 SequenceI copySeq0 = copy.getSequenceAt(0);
903   
904  6 for (int from = start; from <= end; from++)
905    {
906  20 for (int to = from; to <= end; to++)
907    {
908  15 String desc = String.format("%d-%d", from, to);
909  15 SequenceFeature sf = new SequenceFeature("test", desc, from, to, 0f,
910    null);
911  15 sf.setValue("from", Integer.valueOf(from));
912  15 sf.setValue("to", Integer.valueOf(to));
913  15 seq0.addSequenceFeature(sf);
914    }
915    }
916    // sanity check
917  1 List<SequenceFeature> sfs = seq0.getSequenceFeatures();
918  1 assertEquals(func(5), sfs.size());
919  1 assertEquals(sfs, copySeq0.getSequenceFeatures());
920  1 String copySequenceFeatures = copySeq0.getSequenceFeatures().toString();
921   
922    /*
923    * now perform all possible cuts of subranges of columns 1-5
924    * and validate the resulting remaining sequence features!
925    */
926  1 SequenceI[] sqs = new SequenceI[] { seq0 };
927   
928  6 for (int from = 0; from < seq0.getLength(); from++)
929    {
930  20 for (int to = from; to < seq0.getLength(); to++)
931    {
932  15 EditCommand ec = new EditCommand("Cut", Action.CUT, sqs, from,
933    (to - from + 1), alignment);
934  15 final String msg = String.format("Cut %d-%d ", from + 1, to + 1);
935  15 boolean newDatasetSequence = copySeq0.getDatasetSequence() != seq0
936    .getDatasetSequence();
937   
938  15 verifyCut(seq0, from, to, msg, start);
939   
940    /*
941    * verify copy alignment dataset sequence unaffected
942    */
943  15 assertEquals("Original dataset sequence was modified",
944    copySequenceFeatures,
945    copySeq0.getSequenceFeatures().toString());
946   
947    /*
948    * verify any new dataset sequence was added to the
949    * alignment dataset
950    */
951  15 assertEquals("Wrong Dataset size after " + msg,
952  15 newDatasetSequence ? 2 : 1,
953    alignment.getDataset().getHeight());
954   
955    /*
956    * undo and verify all restored
957    */
958  15 AlignmentI[] views = new AlignmentI[] { alignment };
959  15 ec.undoCommand(views);
960  15 sfs = seq0.getSequenceFeatures();
961  15 assertEquals("After undo of " + msg, func(5), sfs.size());
962  15 verifyUndo(from, to, sfs);
963   
964    /*
965    * verify copy alignment dataset sequence still unaffected
966    * and alignment dataset has shrunk (if it was added to)
967    */
968  15 assertEquals("Original dataset sequence was modified",
969    copySequenceFeatures,
970    copySeq0.getSequenceFeatures().toString());
971  15 assertEquals("Wrong Dataset size after Undo of " + msg, 1,
972    alignment.getDataset().getHeight());
973   
974    /*
975    * redo and verify
976    */
977  15 ec.doCommand(views);
978  15 verifyCut(seq0, from, to, msg, start);
979   
980    /*
981    * verify copy alignment dataset sequence unaffected
982    * and any new dataset sequence readded to alignment dataset
983    */
984  15 assertEquals("Original dataset sequence was modified",
985    copySequenceFeatures,
986    copySeq0.getSequenceFeatures().toString());
987  15 assertEquals("Wrong Dataset size after Redo of " + msg,
988  15 newDatasetSequence ? 2 : 1,
989    alignment.getDataset().getHeight());
990   
991    /*
992    * undo ready for next cut
993    */
994  15 ec.undoCommand(views);
995   
996    /*
997    * final verify that copy alignment dataset sequence is still unaffected
998    * and that alignment dataset has shrunk
999    */
1000  15 assertEquals("Original dataset sequence was modified",
1001    copySequenceFeatures,
1002    copySeq0.getSequenceFeatures().toString());
1003  15 assertEquals("Wrong Dataset size after final Undo of " + msg, 1,
1004    alignment.getDataset().getHeight());
1005    }
1006    }
1007    }
1008   
1009    /**
1010    * Verify by inspection that the sequence features left on the sequence after
1011    * a cut match the expected results. The trick to this is that we can parse
1012    * each feature's original start-end positions from its description.
1013    *
1014    * @param seq0
1015    * @param from
1016    * @param to
1017    * @param msg
1018    * @param seqStart
1019    */
 
1020  30 toggle protected void verifyCut(SequenceI seq0, int from, int to,
1021    final String msg, int seqStart)
1022    {
1023  30 List<SequenceFeature> sfs;
1024  30 sfs = seq0.getSequenceFeatures();
1025   
1026  30 Collections.sort(sfs, BY_DESCRIPTION);
1027   
1028    /*
1029    * confirm the number of features has reduced by the
1030    * number of features within the cut region i.e. by
1031    * func(length of cut); exception is a cut at start or end of sequence,
1032    * which retains the original coordinates, dataset sequence
1033    * and all its features
1034    */
1035  30 boolean datasetRetained = from == 0 || to == 4;
1036  30 if (datasetRetained)
1037    {
1038    // dataset and all features retained
1039  18 assertEquals(msg, func(5), sfs.size());
1040    }
1041  12 else if (to - from == 4)
1042    {
1043    // all columns were cut
1044  0 assertTrue(sfs.isEmpty());
1045    }
1046    else
1047    {
1048    // failure in checkFeatureRelocation is more informative!
1049  12 assertEquals(msg + "wrong number of features left",
1050    func(5) - func(to - from + 1), sfs.size());
1051    }
1052   
1053    /*
1054    * inspect individual features
1055    */
1056  30 for (SequenceFeature sf : sfs)
1057    {
1058  420 verifyFeatureRelocation(sf, from + 1, to + 1, !datasetRetained,
1059    seqStart);
1060    }
1061    }
1062   
1063    /**
1064    * Check that after Undo, every feature has start/end that match its original
1065    * "start" and "end" properties
1066    *
1067    * @param from
1068    * @param to
1069    * @param sfs
1070    */
 
1071  15 toggle protected void verifyUndo(int from, int to, List<SequenceFeature> sfs)
1072    {
1073  15 for (SequenceFeature sf : sfs)
1074    {
1075  225 final int oldFrom = ((Integer) sf.getValue("from")).intValue();
1076  225 final int oldTo = ((Integer) sf.getValue("to")).intValue();
1077  225 String msg = String.format("Undo cut of [%d-%d], feature at [%d-%d] ",
1078    from + 1, to + 1, oldFrom, oldTo);
1079  225 assertEquals(msg + "start", oldFrom, sf.getBegin());
1080  225 assertEquals(msg + "end", oldTo, sf.getEnd());
1081    }
1082    }
1083   
1084    /**
1085    * Helper method to check a feature has been correctly relocated after a cut
1086    *
1087    * @param sf
1088    * @param from
1089    * start of cut (first residue cut 1..)
1090    * @param to
1091    * end of cut (last residue cut 1..)
1092    * @param newDataset
1093    * @param seqStart
1094    */
 
1095  420 toggle private void verifyFeatureRelocation(SequenceFeature sf, int from, int to,
1096    boolean newDataset, int seqStart)
1097    {
1098    // TODO handle the gapped sequence case as well
1099  420 int cutSize = to - from + 1;
1100  420 final int oldFrom = ((Integer) sf.getValue("from")).intValue();
1101  420 final int oldTo = ((Integer) sf.getValue("to")).intValue();
1102  420 final int oldFromPosition = oldFrom - seqStart + 1; // 1..
1103  420 final int oldToPosition = oldTo - seqStart + 1; // 1..
1104   
1105  420 String msg = String.format(
1106    "Feature %s relocated to %d-%d after cut of %d-%d",
1107    sf.getDescription(), sf.getBegin(), sf.getEnd(), from, to);
1108  420 if (!newDataset)
1109    {
1110    // dataset retained with all features unchanged
1111  270 assertEquals("0: " + msg, oldFrom, sf.getBegin());
1112  270 assertEquals("0: " + msg, oldTo, sf.getEnd());
1113    }
1114  150 else if (oldToPosition < from)
1115    {
1116    // before cut region so unchanged
1117  30 assertEquals("1: " + msg, oldFrom, sf.getBegin());
1118  30 assertEquals("2: " + msg, oldTo, sf.getEnd());
1119    }
1120  120 else if (oldFromPosition > to)
1121    {
1122    // follows cut region - shift by size of cut
1123  30 assertEquals("3: " + msg, newDataset ? oldFrom - cutSize : oldFrom,
1124    sf.getBegin());
1125  30 assertEquals("4: " + msg, newDataset ? oldTo - cutSize : oldTo,
1126    sf.getEnd());
1127    }
1128  90 else if (oldFromPosition < from && oldToPosition > to)
1129    {
1130    // feature encloses cut region - shrink it right
1131  30 assertEquals("5: " + msg, oldFrom, sf.getBegin());
1132  30 assertEquals("6: " + msg, oldTo - cutSize, sf.getEnd());
1133    }
1134  60 else if (oldFromPosition < from)
1135    {
1136    // feature overlaps left side of cut region - truncated right
1137  30 assertEquals("7: " + msg, from - 1 + seqStart - 1, sf.getEnd());
1138    }
1139  30 else if (oldToPosition > to)
1140    {
1141    // feature overlaps right side of cut region - truncated left
1142  30 assertEquals("8: " + msg, newDataset ? from + seqStart - 1 : to + 1,
1143    sf.getBegin());
1144  30 assertEquals("9: " + msg, newDataset ? from + oldTo - to - 1 : oldTo,
1145    sf.getEnd());
1146    }
1147    else
1148    {
1149    // feature internal to cut - should have been deleted!
1150  0 Assert.fail(msg + " - should have been deleted");
1151    }
1152    }
1153   
1154    /**
1155    * Test a cut action's relocation of sequence features
1156    */
 
1157  1 toggle @Test(groups = { "Functional" })
1158    public void testCut_withFeatures5prime()
1159    {
1160  1 SequenceI seq0 = new Sequence("seq/8-11", "A-BCC");
1161  1 seq0.createDatasetSequence();
1162  1 assertEquals(8, seq0.getStart());
1163  1 seq0.addSequenceFeature(new SequenceFeature("", "", 10, 11, 0f, null));
1164  1 SequenceI[] seqsArray = new SequenceI[] { seq0 };
1165  1 AlignmentI alignment = new Alignment(seqsArray);
1166   
1167    /*
1168    * cut columns of A-B; same dataset sequence is retained, aligned sequence
1169    * start becomes 10
1170    */
1171  1 Edit ec = testee.new Edit(Action.CUT, seqsArray, 0, 3, alignment);
1172  1 EditCommand.cut(ec, new AlignmentI[] { alignment });
1173   
1174    /*
1175    * feature on CC(10-11) should still be on CC(10-11)
1176    */
1177  1 assertSame(seq0, alignment.getSequenceAt(0));
1178  1 assertEquals(10, seq0.getStart());
1179  1 List<SequenceFeature> sfs = seq0.getSequenceFeatures();
1180  1 assertEquals(1, sfs.size());
1181  1 SequenceFeature sf = sfs.get(0);
1182  1 assertEquals(10, sf.getBegin());
1183  1 assertEquals(11, sf.getEnd());
1184    }
1185   
 
1186  3 toggle private SequenceI mkDs(SequenceI as)
1187    {
1188  3 SequenceI ds = as.createDatasetSequence();
1189  3 ds.setSequence(ds.getSequenceAsString().toUpperCase(Locale.ROOT));
1190  3 return ds;
1191    }
1192   
1193    /**
1194    * Test that mimics 'remove all gapped columns' action. This generates a
1195    * series Delete Gap edits that each act on all sequences that share a gapped
1196    * column region.
1197    */
 
1198  1 toggle @Test(groups = { "Functional" })
1199    public void testLeftRight_Justify_and_preserves_gaps()
1200    {
1201  1 EditCommand command = new EditCommand();
1202  1 String original1 = "--ABc--DEF";
1203  1 String original2 = "-G-Hi--J";
1204  1 String original3 = "-M-No--PQ";
1205   
1206    /*
1207    * Set up the sequence array for operations
1208    */
1209  1 SequenceI seq1 = new Sequence("sq1", original1);
1210  1 SequenceI ds1 = mkDs(seq1);
1211    /*
1212    * and check we are preserving data - if the calls below fail, something has broken the Jalview dataset derivation process
1213    */
1214  1 assertEquals("ABCDEF", seq1.getDatasetSequence().getSequenceAsString());
1215  1 assertEquals(original1, seq1.getSequenceAsString());
1216  1 SequenceI seq2 = new Sequence("sq2", original2);
1217  1 SequenceI ds2 = mkDs(seq2);
1218  1 SequenceI seq3 = new Sequence("sq3", original3);
1219  1 SequenceI ds3 = mkDs(seq3);
1220  1 List<SequenceI> sqs = Arrays.asList(seq1, seq2, seq3);
1221  1 Alignment al = new Alignment(sqs.toArray(new SequenceI[0]));
1222  1 EditCommand lefj = new JustifyLeftOrRightCommand("Left J", true, sqs, 1,
1223    7, al);
1224  1 String exp = "-ABcD---EF";
1225    // check without case conservation
1226  1 assertEquals(exp.toUpperCase(Locale.ROOT),
1227    seq1.getSequenceAsString().toUpperCase(Locale.ROOT));
1228    // check case
1229  1 assertEquals(exp, seq1.getSequenceAsString());
1230    // and other seqs
1231  1 assertEquals("-GHiJ---", seq2.getSequenceAsString());
1232  1 assertEquals("-MNoP---Q", seq3.getSequenceAsString());
1233  1 lefj.undoCommand(new AlignmentI[] { al });
1234  1 assertEquals(original3, seq3.getSequenceAsString());
1235  1 assertEquals(original1, seq1.getSequenceAsString());
1236  1 assertEquals(original2, seq2.getSequenceAsString());
1237   
1238  1 EditCommand righj = new JustifyLeftOrRightCommand("Right J", false, sqs,
1239    2, 7, al);
1240  1 assertEquals("----ABcDEF", seq1.getSequenceAsString());
1241  1 assertEquals("-G---HiJ", seq2.getSequenceAsString());
1242  1 assertEquals("-M---NoPQ", seq3.getSequenceAsString());
1243  1 righj.undoCommand(new AlignmentI[] { al });
1244  1 assertEquals(original3, seq3.getSequenceAsString());
1245  1 assertEquals(original1, seq1.getSequenceAsString());
1246  1 assertEquals(original2, seq2.getSequenceAsString());
1247   
1248    }
1249    }