e59719c2c7d14e26594fda55d53733828c1328d9
[jalview.git] / test / jalview / datamodel / ColumnSelectionTest.java
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.datamodel;
22
23 import static org.testng.AssertJUnit.assertEquals;
24 import static org.testng.AssertJUnit.assertFalse;
25 import static org.testng.AssertJUnit.assertNotSame;
26 import static org.testng.AssertJUnit.assertSame;
27 import static org.testng.AssertJUnit.assertTrue;
28
29 import java.util.Arrays;
30 import java.util.BitSet;
31 import java.util.List;
32
33 import org.testng.annotations.Test;
34
35 public class ColumnSelectionTest
36 {
37
38   @Test(groups = { "Functional" })
39   public void testAddElement()
40   {
41     ColumnSelection cs = new ColumnSelection();
42     cs.addElement(2);
43     cs.addElement(5);
44     cs.addElement(3);
45     cs.addElement(5); // ignored
46     List<Integer> sel = cs.getSelected();
47     assertEquals("[2, 5, 3]", sel.toString());
48   }
49
50   /**
51    * Test the remove method - in particular to verify that remove(int i) removes
52    * the element whose value is i, _NOT_ the i'th element.
53    */
54   @Test(groups = { "Functional" })
55   public void testRemoveElement()
56   {
57     ColumnSelection cs = new ColumnSelection();
58     cs.addElement(2);
59     cs.addElement(5);
60
61     // removing elements not in the list has no effect
62     cs.removeElement(0);
63     cs.removeElement(1);
64     List<Integer> sel = cs.getSelected();
65     assertEquals(2, sel.size());
66     assertEquals(new Integer(2), sel.get(0));
67     assertEquals(new Integer(5), sel.get(1));
68
69     // removing an element in the list removes it
70     cs.removeElement(2);
71     // ...but not from the copy list!
72     assertEquals(2, sel.size());
73     sel = cs.getSelected();
74     assertEquals(1, sel.size());
75     assertEquals(new Integer(5), sel.get(0));
76   }
77
78   /**
79    * Test the method that finds the visible column position of an alignment
80    * column, allowing for hidden columns.
81    */
82   @Test(groups = { "Functional" })
83   public void testFindColumnPosition()
84   {
85     ColumnSelection cs = new ColumnSelection();
86     assertEquals(5, cs.findColumnPosition(5));
87
88     // hiding column 6 makes no difference
89     cs.hideColumns(6, 6);
90     assertEquals(5, cs.findColumnPosition(5));
91
92     // hiding column 4 moves column 5 to column 4
93     cs.hideColumns(4, 4);
94     assertEquals(4, cs.findColumnPosition(5));
95
96     // hiding columns 1 and 2 moves column 5 to column 2
97     cs.hideColumns(1, 2);
98     assertEquals(2, cs.findColumnPosition(5));
99   }
100
101   /**
102    * Test the code used to locate the reference sequence ruler origin
103    */
104   @Test(groups = { "Functional" })
105   public void testLocateVisibleBoundsofSequence()
106   {
107     ColumnSelection cs = new ColumnSelection();
108     SequenceI seq = new Sequence("RefSeq", "-A-SD-ASD--E---");
109     assertEquals(2, seq.findIndex(seq.getStart()));
110
111     // no hidden columns
112     assertEquals(
113             Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1,
114                 seq.findIndex(seq.getEnd()) - 1, seq.getStart(),
115                 seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
116                 seq.findIndex(seq.getEnd()) - 1 }),
117             Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
118
119     // hidden column on gap after end of sequence - should not affect bounds
120     cs.hideColumns(13);
121     assertEquals(
122             Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1,
123                 seq.findIndex(seq.getEnd()) - 1, seq.getStart(),
124                 seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
125                 seq.findIndex(seq.getEnd()) - 1 }),
126             Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
127
128     cs.revealAllHiddenColumns();
129     // hidden column on gap before beginning of sequence - should vis bounds by
130     // one
131     cs.hideColumns(0);
132     assertEquals(
133             Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 2,
134                 seq.findIndex(seq.getEnd()) - 2, seq.getStart(),
135                 seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
136                 seq.findIndex(seq.getEnd()) - 1 }),
137             Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
138
139     cs.revealAllHiddenColumns();
140     // hide columns around most of sequence - leave one residue remaining
141     cs.hideColumns(1, 3);
142     cs.hideColumns(6, 11);
143     assertEquals("-D",
144             cs.getVisibleSequenceStrings(0, 5, new SequenceI[] { seq })[0]);
145     assertEquals(
146             Arrays.toString(new int[] { 1, 1, 3, 3,
147                 seq.findIndex(seq.getStart()) - 1,
148                 seq.findIndex(seq.getEnd()) - 1 }),
149             Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
150     cs.revealAllHiddenColumns();
151
152     // hide whole sequence - should just get location of hidden region
153     // containing sequence
154     cs.hideColumns(1, 11);
155     assertEquals(
156             Arrays.toString(new int[] { 0, 1, 0, 0,
157                 seq.findIndex(seq.getStart()) - 1,
158                 seq.findIndex(seq.getEnd()) - 1 }),
159             Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
160
161   }
162
163   @Test(groups={"Functional"})
164   public void testLocateVisibleBoundsPathologicals()
165   {
166     // test some pathological cases we missed
167     AlignmentI al = new Alignment(new SequenceI[] { new Sequence("refseqGaptest","KTDVTI----------NFI-----G----L")});
168     ColumnSelection cs = new ColumnSelection();
169     cs.hideInsertionsFor(al.getSequenceAt(0));
170     assertEquals(
171             "G",
172             ""
173                     + al.getSequenceAt(0).getCharAt(
174                             cs.adjustForHiddenColumns(9)));
175
176
177   }
178   @Test(groups = { "Functional" })
179   public void testHideColumns()
180   {
181     ColumnSelection cs = new ColumnSelection();
182     cs.hideColumns(5);
183     List<int[]> hidden = cs.getHiddenColumns();
184     assertEquals(1, hidden.size());
185     assertEquals("[5, 5]", Arrays.toString(hidden.get(0)));
186
187     cs.hideColumns(3);
188     assertEquals(2, hidden.size());
189     // two hidden ranges, in order:
190     assertSame(hidden, cs.getHiddenColumns());
191     assertEquals("[3, 3]", Arrays.toString(hidden.get(0)));
192     assertEquals("[5, 5]", Arrays.toString(hidden.get(1)));
193
194     // hiding column 4 expands [3, 3] to [3, 4]
195     // not fancy enough to coalesce this into [3, 5] though
196     cs.hideColumns(4);
197     hidden = cs.getHiddenColumns();
198     assertEquals(2, hidden.size());
199     assertEquals("[3, 4]", Arrays.toString(hidden.get(0)));
200     assertEquals("[5, 5]", Arrays.toString(hidden.get(1)));
201
202     // clear hidden columns (note they are added to selected)
203     cs.revealAllHiddenColumns();
204     // it is now actually null but getter returns an empty list
205     assertTrue(cs.getHiddenColumns().isEmpty());
206
207     cs.hideColumns(3, 6);
208     hidden = cs.getHiddenColumns();
209     int[] firstHiddenRange = hidden.get(0);
210     assertEquals("[3, 6]", Arrays.toString(firstHiddenRange));
211
212     // adding a subrange of already hidden should do nothing
213     cs.hideColumns(4, 5);
214     assertEquals(1, hidden.size());
215     assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
216     cs.hideColumns(3, 5);
217     assertEquals(1, hidden.size());
218     assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
219     cs.hideColumns(4, 6);
220     assertEquals(1, hidden.size());
221     assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
222     cs.hideColumns(3, 6);
223     assertEquals(1, hidden.size());
224     assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
225
226     cs.revealAllHiddenColumns();
227     cs.hideColumns(2, 4);
228     hidden = cs.getHiddenColumns();
229     assertEquals(1, hidden.size());
230     assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
231
232     // extend contiguous with 2 positions overlap
233     cs.hideColumns(3, 5);
234     assertEquals(1, hidden.size());
235     assertEquals("[2, 5]", Arrays.toString(hidden.get(0)));
236
237     // extend contiguous with 1 position overlap
238     cs.hideColumns(5, 6);
239     assertEquals(1, hidden.size());
240     assertEquals("[2, 6]", Arrays.toString(hidden.get(0)));
241
242     // extend contiguous with overlap both ends:
243     cs.hideColumns(1, 7);
244     assertEquals(1, hidden.size());
245     assertEquals("[1, 7]", Arrays.toString(hidden.get(0)));
246   }
247
248   /**
249    * Test the method that hides a specified column including any adjacent
250    * selected columns. This is a convenience method for the case where multiple
251    * column regions are selected and then hidden using menu option View | Hide |
252    * Selected Columns.
253    */
254   @Test(groups = { "Functional" })
255   public void testHideColumns_withSelection()
256   {
257     ColumnSelection cs = new ColumnSelection();
258     // select columns 4-6
259     cs.addElement(4);
260     cs.addElement(5);
261     cs.addElement(6);
262     // hide column 5 (and adjacent):
263     cs.hideColumns(5);
264     // 4,5,6 now hidden:
265     List<int[]> hidden = cs.getHiddenColumns();
266     assertEquals(1, hidden.size());
267     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
268     // none now selected:
269     assertTrue(cs.getSelected().isEmpty());
270
271     // repeat, hiding column 4 (5 and 6)
272     cs = new ColumnSelection();
273     cs.addElement(4);
274     cs.addElement(5);
275     cs.addElement(6);
276     cs.hideColumns(4);
277     hidden = cs.getHiddenColumns();
278     assertEquals(1, hidden.size());
279     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
280     assertTrue(cs.getSelected().isEmpty());
281
282     // repeat, hiding column (4, 5 and) 6
283     cs = new ColumnSelection();
284     cs.addElement(4);
285     cs.addElement(5);
286     cs.addElement(6);
287     cs.hideColumns(6);
288     hidden = cs.getHiddenColumns();
289     assertEquals(1, hidden.size());
290     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
291     assertTrue(cs.getSelected().isEmpty());
292
293     // repeat, with _only_ adjacent columns selected
294     cs = new ColumnSelection();
295     cs.addElement(4);
296     cs.addElement(6);
297     cs.hideColumns(5);
298     hidden = cs.getHiddenColumns();
299     assertEquals(1, hidden.size());
300     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
301     assertTrue(cs.getSelected().isEmpty());
302   }
303
304   /**
305    * Test the method that hides all (possibly disjoint) selected column ranges
306    */
307   @Test(groups = { "Functional" })
308   public void testHideSelectedColumns()
309   {
310     ColumnSelection cs = new ColumnSelection();
311     int[] sel = { 2, 3, 4, 7, 8, 9, 20, 21, 22 };
312     for (int col : sel)
313     {
314       cs.addElement(col);
315     }
316     cs.hideColumns(15, 18);
317
318     cs.hideSelectedColumns();
319     assertTrue(cs.getSelected().isEmpty());
320     List<int[]> hidden = cs.getHiddenColumns();
321     assertEquals(4, hidden.size());
322     assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
323     assertEquals("[7, 9]", Arrays.toString(hidden.get(1)));
324     assertEquals("[15, 18]", Arrays.toString(hidden.get(2)));
325     assertEquals("[20, 22]", Arrays.toString(hidden.get(3)));
326   }
327
328   /**
329    * Test the method that gets runs of selected columns ordered by column. If
330    * this fails, HideSelectedColumns may also fail
331    */
332   @Test(groups = { "Functional" })
333   public void testGetSelectedRanges()
334   {
335     /*
336      * getSelectedRanges returns ordered columns regardless
337      * of the order in which they are added
338      */
339     ColumnSelection cs = new ColumnSelection();
340     int[] sel = { 4, 3, 7, 21, 9, 20, 8, 22, 2 };
341     for (int col : sel)
342     {
343       cs.addElement(col);
344     }
345     List<int[]> range;
346     range = cs.getSelectedRanges();
347     assertEquals(3, range.size());
348     assertEquals("[2, 4]", Arrays.toString(range.get(0)));
349     assertEquals("[7, 9]", Arrays.toString(range.get(1)));
350     assertEquals("[20, 22]", Arrays.toString(range.get(2)));
351     cs.addElement(0);
352     cs.addElement(1);
353     range = cs.getSelectedRanges();
354     assertEquals(3, range.size());
355     assertEquals("[0, 4]", Arrays.toString(range.get(0)));
356   }
357
358   /**
359    * Test the method that reveals a range of hidden columns given the start
360    * column of the range
361    */
362   @Test(groups = { "Functional" })
363   public void testRevealHiddenColumns()
364   {
365     ColumnSelection cs = new ColumnSelection();
366     cs.hideColumns(5, 8);
367     cs.addElement(10);
368     cs.revealHiddenColumns(5);
369     // hidden columns list now null but getter returns empty list:
370     assertTrue(cs.getHiddenColumns().isEmpty());
371     // revealed columns are marked as selected (added to selection):
372     assertEquals("[10, 5, 6, 7, 8]", cs.getSelected().toString());
373
374     // calling with a column other than the range start does nothing:
375     cs = new ColumnSelection();
376     cs.hideColumns(5, 8);
377     List<int[]> hidden = cs.getHiddenColumns();
378     cs.revealHiddenColumns(6);
379     assertSame(hidden, cs.getHiddenColumns());
380     assertTrue(cs.getSelected().isEmpty());
381   }
382
383   @Test(groups = { "Functional" })
384   public void testRevealAllHiddenColumns()
385   {
386     ColumnSelection cs = new ColumnSelection();
387     cs.hideColumns(5, 8);
388     cs.hideColumns(2, 3);
389     cs.addElement(11);
390     cs.addElement(1);
391     cs.revealAllHiddenColumns();
392
393     /*
394      * revealing hidden columns adds them (in order) to the (unordered)
395      * selection list
396      */
397     assertTrue(cs.getHiddenColumns().isEmpty());
398     assertEquals("[11, 1, 2, 3, 5, 6, 7, 8]", cs.getSelected().toString());
399   }
400
401   @Test(groups = { "Functional" })
402   public void testIsVisible()
403   {
404     ColumnSelection cs = new ColumnSelection();
405     cs.hideColumns(2, 4);
406     cs.hideColumns(6, 7);
407     assertTrue(cs.isVisible(0));
408     assertTrue(cs.isVisible(-99));
409     assertTrue(cs.isVisible(1));
410     assertFalse(cs.isVisible(2));
411     assertFalse(cs.isVisible(3));
412     assertFalse(cs.isVisible(4));
413     assertTrue(cs.isVisible(5));
414     assertFalse(cs.isVisible(6));
415     assertFalse(cs.isVisible(7));
416   }
417
418   @Test(groups = { "Functional" })
419   public void testGetVisibleContigs()
420   {
421     ColumnSelection cs = new ColumnSelection();
422     cs.hideColumns(3, 6);
423     cs.hideColumns(8, 9);
424     cs.hideColumns(12, 12);
425
426     // start position is inclusive, end position exclusive:
427     int[] visible = cs.getVisibleContigs(1, 13);
428     assertEquals("[1, 2, 7, 7, 10, 11]", Arrays.toString(visible));
429
430     visible = cs.getVisibleContigs(4, 14);
431     assertEquals("[7, 7, 10, 11, 13, 13]", Arrays.toString(visible));
432
433     visible = cs.getVisibleContigs(3, 10);
434     assertEquals("[7, 7]", Arrays.toString(visible));
435
436     visible = cs.getVisibleContigs(4, 6);
437     assertEquals("[]", Arrays.toString(visible));
438   }
439
440   @Test(groups = { "Functional" })
441   public void testInvertColumnSelection()
442   {
443     ColumnSelection cs = new ColumnSelection();
444     cs.addElement(4);
445     cs.addElement(6);
446     cs.addElement(8);
447     cs.hideColumns(3, 3);
448     cs.hideColumns(6, 6);
449
450     // invert selection from start (inclusive) to end (exclusive)
451     // hidden columns are _not_ changed
452     cs.invertColumnSelection(2, 9);
453     assertEquals("[2, 5, 7]", cs.getSelected().toString());
454
455     cs.invertColumnSelection(1, 9);
456     assertEquals("[1, 4, 8]", cs.getSelected().toString());
457   }
458
459   @Test(groups = { "Functional" })
460   public void testMaxColumnSelection()
461   {
462     ColumnSelection cs = new ColumnSelection();
463     cs.addElement(0);
464     cs.addElement(513);
465     cs.addElement(1);
466     assertEquals(513, cs.getMax());
467     cs.removeElement(513);
468     assertEquals(1, cs.getMax());
469     cs.removeElement(1);
470     assertEquals(0, cs.getMax());
471     cs.addElement(512);
472     cs.addElement(513);
473     assertEquals(513, cs.getMax());
474
475   }
476
477   @Test(groups = { "Functional" })
478   public void testMinColumnSelection()
479   {
480     ColumnSelection cs = new ColumnSelection();
481     cs.addElement(0);
482     cs.addElement(513);
483     cs.addElement(1);
484     assertEquals(0, cs.getMin());
485     cs.removeElement(0);
486     assertEquals(1, cs.getMin());
487     cs.addElement(0);
488     assertEquals(0, cs.getMin());
489   }
490
491   @Test(groups = { "Functional" })
492   public void testEquals()
493   {
494     ColumnSelection cs = new ColumnSelection();
495     cs.addElement(0);
496     cs.addElement(513);
497     cs.addElement(1);
498     cs.hideColumns(3);
499     cs.hideColumns(7);
500     cs.hideColumns(5,9);
501
502     // same selections added in a different order
503     ColumnSelection cs2 = new ColumnSelection();
504     cs2.addElement(1);
505     cs2.addElement(513);
506     cs2.addElement(0);
507
508     // with no hidden columns
509     assertFalse(cs.equals(cs2));
510     assertFalse(cs2.equals(cs));
511
512     // with hidden columns added in a different order
513     cs2.hideColumns(6, 9);
514     cs2.hideColumns(5, 8);
515     cs2.hideColumns(3);
516     
517     assertTrue(cs.equals(cs2));
518     assertTrue(cs.equals(cs));
519     assertTrue(cs2.equals(cs));
520     assertTrue(cs2.equals(cs2));
521
522     cs2.addElement(12);
523     assertFalse(cs.equals(cs2));
524     assertFalse(cs2.equals(cs));
525
526     cs2.removeElement(12);
527     assertTrue(cs.equals(cs2));
528
529     cs2.hideColumns(88);
530     assertFalse(cs.equals(cs2));
531     /*
532      * unhiding a column adds it to selection!
533      */
534     cs2.revealHiddenColumns(88);
535     assertFalse(cs.equals(cs2));
536     cs.addElement(88);
537     assertTrue(cs.equals(cs2));
538   }
539
540   /**
541    * Test the method that returns selected columns, in the order in which they
542    * were added
543    */
544   @Test(groups = { "Functional" })
545   public void testGetSelection()
546   {
547     ColumnSelection cs = new ColumnSelection();
548     int[] sel = { 4, 3, 7, 21 };
549     for (int col : sel)
550     {
551       cs.addElement(col);
552     }
553
554     List<Integer> selected1 = cs.getSelected();
555     assertEquals(4, selected1.size());
556
557     /*
558      * getSelected returns a copy, verify the list
559      * is externally immutable
560      */
561     selected1.clear();
562     List<Integer> selected2 = cs.getSelected();
563     assertNotSame(selected1, selected2);
564     assertEquals(4, selected2.size());
565     int i = 0;
566     for (int col : sel)
567     {
568       assertEquals(col, selected2.get(i++).intValue());
569     }
570
571     cs.removeElement(7);
572     cs.addElement(1);
573     cs.removeElement(4);
574
575     List<Integer> selected3 = cs.getSelected();
576     assertEquals(3, selected3.size());
577     assertEquals(3, selected3.get(0).intValue());
578     assertEquals(21, selected3.get(1).intValue());
579     assertEquals(1, selected3.get(2).intValue());
580   }
581
582   @Test(groups = "Functional")
583   public void testMarkColumns()
584   {
585     ColumnSelection cs = new ColumnSelection();
586     cs.addElement(5); // this will be cleared
587     BitSet toMark = new BitSet();
588     toMark.set(1);
589     toMark.set(3);
590     toMark.set(6);
591     toMark.set(9);
592
593     assertTrue(cs.markColumns(toMark, 3, 8, false, false, false));
594     List<Integer> selected = cs.getSelected();
595     assertEquals(2, selected.size());
596     assertTrue(selected.contains(3));
597     assertTrue(selected.contains(6));
598   }
599
600   @Test(groups = "Functional")
601   public void testMarkColumns_extend()
602   {
603     ColumnSelection cs = new ColumnSelection();
604     cs.addElement(1);
605     cs.addElement(5);
606     BitSet toMark = new BitSet();
607     toMark.set(1);
608     toMark.set(3);
609     toMark.set(6);
610     toMark.set(9);
611
612     /*
613      * extending selection of {3, 6} should leave {1, 3, 5, 6} selected
614      */
615     assertTrue(cs.markColumns(toMark, 3, 8, false, true, false));
616     List<Integer> selected = cs.getSelected();
617     assertEquals(4, selected.size());
618     assertTrue(selected.contains(1));
619     assertTrue(selected.contains(3));
620     assertTrue(selected.contains(5));
621     assertTrue(selected.contains(6));
622   }
623
624   @Test(groups = "Functional")
625   public void testMarkColumns_invert()
626   {
627     ColumnSelection cs = new ColumnSelection();
628     cs.addElement(5); // this will be cleared
629     BitSet toMark = new BitSet();
630     toMark.set(1);
631     toMark.set(3);
632     toMark.set(6);
633     toMark.set(9);
634
635     /*
636      * inverted selection of {3, 6} should select {4, 5, 7, 8}
637      */
638     assertTrue(cs.markColumns(toMark, 3, 8, true, false, false));
639     List<Integer> selected = cs.getSelected();
640     assertEquals(4, selected.size());
641     assertTrue(selected.contains(4));
642     assertTrue(selected.contains(5));
643     assertTrue(selected.contains(7));
644     assertTrue(selected.contains(8));
645   }
646
647   @Test(groups = "Functional")
648   public void testMarkColumns_toggle()
649   {
650     ColumnSelection cs = new ColumnSelection();
651     cs.addElement(1); // outside change range
652     cs.addElement(3);
653     cs.addElement(4);
654     cs.addElement(10); // outside change range
655     BitSet toMark = new BitSet();
656     toMark.set(1);
657     toMark.set(3);
658     toMark.set(6);
659     toMark.set(9);
660
661     /*
662      * toggling state of {3, 6} should leave {1, 4, 6, 10} selected
663      */
664     assertTrue(cs.markColumns(toMark, 3, 8, false, false, true));
665     List<Integer> selected = cs.getSelected();
666     assertEquals(4, selected.size());
667     assertTrue(selected.contains(1));
668     assertTrue(selected.contains(4));
669     assertTrue(selected.contains(6));
670     assertTrue(selected.contains(10));
671   }
672 }