JAL-2370 fix bug when hiding a range including two or more hidden ranges
[jalview.git] / test / jalview / datamodel / ColumnSelectionTest.java
index 1a7ae32..4faa482 100644 (file)
@@ -24,15 +24,29 @@ import static org.testng.AssertJUnit.assertEquals;
 import static org.testng.AssertJUnit.assertFalse;
 import static org.testng.AssertJUnit.assertSame;
 import static org.testng.AssertJUnit.assertTrue;
+import static org.testng.AssertJUnit.fail;
+
+import jalview.gui.JvOptionPane;
 
 import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.ConcurrentModificationException;
 import java.util.List;
 
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 public class ColumnSelectionTest
 {
 
+  @BeforeClass(alwaysRun = true)
+  public void setUpJvOptionPane()
+  {
+    JvOptionPane.setInteractiveMode(false);
+    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+  }
+
   @Test(groups = { "Functional" })
   public void testAddElement()
   {
@@ -66,6 +80,9 @@ public class ColumnSelectionTest
 
     // removing an element in the list removes it
     cs.removeElement(2);
+    // ...and also from the read-only view
+    assertEquals(1, sel.size());
+    sel = cs.getSelected();
     assertEquals(1, sel.size());
     assertEquals(new Integer(5), sel.get(0));
   }
@@ -155,11 +172,12 @@ public class ColumnSelectionTest
 
   }
 
-  @Test(groups={"Functional"})
+  @Test(groups = { "Functional" })
   public void testLocateVisibleBoundsPathologicals()
   {
     // test some pathological cases we missed
-    AlignmentI al = new Alignment(new SequenceI[] { new Sequence("refseqGaptest","KTDVTI----------NFI-----G----L")});
+    AlignmentI al = new Alignment(new SequenceI[] { new Sequence(
+            "refseqGaptest", "KTDVTI----------NFI-----G----L") });
     ColumnSelection cs = new ColumnSelection();
     cs.hideInsertionsFor(al.getSequenceAt(0));
     assertEquals(
@@ -168,8 +186,8 @@ public class ColumnSelectionTest
                     + al.getSequenceAt(0).getCharAt(
                             cs.adjustForHiddenColumns(9)));
 
-
   }
+
   @Test(groups = { "Functional" })
   public void testHideColumns()
   {
@@ -187,12 +205,11 @@ public class ColumnSelectionTest
     assertEquals("[5, 5]", Arrays.toString(hidden.get(1)));
 
     // hiding column 4 expands [3, 3] to [3, 4]
-    // not fancy enough to coalesce this into [3, 5] though
+    // and merges to [5, 5] to make [3, 5]
     cs.hideColumns(4);
     hidden = cs.getHiddenColumns();
-    assertEquals(2, hidden.size());
-    assertEquals("[3, 4]", Arrays.toString(hidden.get(0)));
-    assertEquals("[5, 5]", Arrays.toString(hidden.get(1)));
+    assertEquals(1, hidden.size());
+    assertEquals("[3, 5]", Arrays.toString(hidden.get(0)));
 
     // clear hidden columns (note they are added to selected)
     cs.revealAllHiddenColumns();
@@ -325,10 +342,14 @@ public class ColumnSelectionTest
    * this fails, HideSelectedColumns may also fail
    */
   @Test(groups = { "Functional" })
-  public void testgetSelectedRanges()
+  public void testGetSelectedRanges()
   {
+    /*
+     * getSelectedRanges returns ordered columns regardless
+     * of the order in which they are added
+     */
     ColumnSelection cs = new ColumnSelection();
-    int[] sel = { 2, 3, 4, 7, 8, 9, 20, 21, 22 };
+    int[] sel = { 4, 3, 7, 21, 9, 20, 8, 22, 2 };
     for (int col : sel)
     {
       cs.addElement(col);
@@ -488,7 +509,7 @@ public class ColumnSelectionTest
     cs.addElement(1);
     cs.hideColumns(3);
     cs.hideColumns(7);
-    cs.hideColumns(5,9);
+    cs.hideColumns(5, 9);
 
     // same selections added in a different order
     ColumnSelection cs2 = new ColumnSelection();
@@ -504,7 +525,7 @@ public class ColumnSelectionTest
     cs2.hideColumns(6, 9);
     cs2.hideColumns(5, 8);
     cs2.hideColumns(3);
-    
+
     assertTrue(cs.equals(cs2));
     assertTrue(cs.equals(cs));
     assertTrue(cs2.equals(cs));
@@ -527,4 +548,271 @@ public class ColumnSelectionTest
     cs.addElement(88);
     assertTrue(cs.equals(cs2));
   }
+
+  /**
+   * Test the method that returns selected columns, in the order in which they
+   * were added
+   */
+  @Test(groups = { "Functional" })
+  public void testGetSelected()
+  {
+    ColumnSelection cs = new ColumnSelection();
+    int[] sel = { 4, 3, 7, 21 };
+    for (int col : sel)
+    {
+      cs.addElement(col);
+    }
+
+    List<Integer> selected = cs.getSelected();
+    assertEquals(4, selected.size());
+    assertEquals("[4, 3, 7, 21]", selected.toString());
+
+    /*
+     * getSelected returns a read-only view of the list
+     * verify the view follows any changes in it
+     */
+    cs.removeElement(7);
+    cs.addElement(1);
+    cs.removeElement(4);
+    assertEquals("[3, 21, 1]", selected.toString());
+  }
+
+  /**
+   * Test to verify that the list returned by getSelection cannot be modified
+   */
+  @Test(groups = { "Functional" })
+  public void testGetSelected_isReadOnly()
+  {
+    ColumnSelection cs = new ColumnSelection();
+    cs.addElement(3);
+
+    List<Integer> selected = cs.getSelected();
+    try
+    {
+      selected.clear();
+      fail("expected exception");
+    } catch (UnsupportedOperationException e)
+    {
+      // expected
+    }
+    try
+    {
+      selected.add(1);
+      fail("expected exception");
+    } catch (UnsupportedOperationException e)
+    {
+      // expected
+    }
+    try
+    {
+      selected.remove(3);
+      fail("expected exception");
+    } catch (UnsupportedOperationException e)
+    {
+      // expected
+    }
+    try
+    {
+      Collections.sort(selected);
+      fail("expected exception");
+    } catch (UnsupportedOperationException e)
+    {
+      // expected
+    }
+  }
+
+  /**
+   * Test that demonstrates a ConcurrentModificationException is thrown if you
+   * change the selection while iterating over it
+   */
+  @Test(
+    groups = "Functional",
+    expectedExceptions = { ConcurrentModificationException.class })
+  public void testGetSelected_concurrentModification()
+  {
+    ColumnSelection cs = new ColumnSelection();
+    cs.addElement(0);
+    cs.addElement(1);
+    cs.addElement(2);
+
+    /*
+     * simulate changing the list under us (e.g. in a separate
+     * thread) while iterating over it -> ConcurrentModificationException
+     */
+    List<Integer> selected = cs.getSelected();
+    for (Integer col : selected)
+    {
+      if (col.intValue() == 0)
+      {
+        cs.removeElement(1);
+      }
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testMarkColumns()
+  {
+    ColumnSelection cs = new ColumnSelection();
+    cs.addElement(5); // this will be cleared
+    BitSet toMark = new BitSet();
+    toMark.set(1);
+    toMark.set(3);
+    toMark.set(6);
+    toMark.set(9);
+
+    assertTrue(cs.markColumns(toMark, 3, 8, false, false, false));
+    List<Integer> selected = cs.getSelected();
+    assertEquals(2, selected.size());
+    assertTrue(selected.contains(3));
+    assertTrue(selected.contains(6));
+  }
+
+  @Test(groups = "Functional")
+  public void testMarkColumns_extend()
+  {
+    ColumnSelection cs = new ColumnSelection();
+    cs.addElement(1);
+    cs.addElement(5);
+    BitSet toMark = new BitSet();
+    toMark.set(1);
+    toMark.set(3);
+    toMark.set(6);
+    toMark.set(9);
+
+    /*
+     * extending selection of {3, 6} should leave {1, 3, 5, 6} selected
+     */
+    assertTrue(cs.markColumns(toMark, 3, 8, false, true, false));
+    List<Integer> selected = cs.getSelected();
+    assertEquals(4, selected.size());
+    assertTrue(selected.contains(1));
+    assertTrue(selected.contains(3));
+    assertTrue(selected.contains(5));
+    assertTrue(selected.contains(6));
+  }
+
+  @Test(groups = "Functional")
+  public void testMarkColumns_invert()
+  {
+    ColumnSelection cs = new ColumnSelection();
+    cs.addElement(5); // this will be cleared
+    BitSet toMark = new BitSet();
+    toMark.set(1);
+    toMark.set(3);
+    toMark.set(6);
+    toMark.set(9);
+
+    /*
+     * inverted selection of {3, 6} should select {4, 5, 7, 8}
+     */
+    assertTrue(cs.markColumns(toMark, 3, 8, true, false, false));
+    List<Integer> selected = cs.getSelected();
+    assertEquals(4, selected.size());
+    assertTrue(selected.contains(4));
+    assertTrue(selected.contains(5));
+    assertTrue(selected.contains(7));
+    assertTrue(selected.contains(8));
+  }
+
+  @Test(groups = "Functional")
+  public void testMarkColumns_toggle()
+  {
+    ColumnSelection cs = new ColumnSelection();
+    cs.addElement(1); // outside change range
+    cs.addElement(3);
+    cs.addElement(4);
+    cs.addElement(10); // outside change range
+    BitSet toMark = new BitSet();
+    toMark.set(1);
+    toMark.set(3);
+    toMark.set(6);
+    toMark.set(9);
+
+    /*
+     * toggling state of {3, 6} should leave {1, 4, 6, 10} selected
+     */
+    assertTrue(cs.markColumns(toMark, 3, 8, false, false, true));
+    List<Integer> selected = cs.getSelected();
+    assertEquals(4, selected.size());
+    assertTrue(selected.contains(1));
+    assertTrue(selected.contains(4));
+    assertTrue(selected.contains(6));
+    assertTrue(selected.contains(10));
+  }
+
+  @Test(groups = "Functional")
+  public void testCopyConstructor()
+  {
+    ColumnSelection cs = new ColumnSelection();
+    cs.addElement(3);
+    cs.addElement(1);
+    cs.hideColumns(10, 11);
+    cs.hideColumns(5, 7);
+    assertEquals("[5, 7]", Arrays.toString(cs.getHiddenColumns().get(0)));
+
+    ColumnSelection cs2 = new ColumnSelection(cs);
+    assertTrue(cs2.hasSelectedColumns());
+    assertTrue(cs2.hasHiddenColumns());
+    // order of column selection is preserved
+    assertEquals("[3, 1]", cs2.getSelected().toString());
+    assertEquals(2, cs2.getHiddenColumns().size());
+    // hidden columns are held in column order
+    assertEquals("[5, 7]", Arrays.toString(cs2.getHiddenColumns().get(0)));
+    assertEquals("[10, 11]", Arrays.toString(cs2.getHiddenColumns().get(1)));
+  }
+
+  /**
+   * Test for the case when a hidden range encloses more one already hidden
+   * range
+   */
+  @Test(groups = { "Functional" })
+  public void testHideColumns_subsumingHidden()
+  {
+    /*
+     * JAL-2370 bug scenario:
+     * two hidden ranges subsumed by a third
+     */
+    ColumnSelection cs = new ColumnSelection();
+    cs.hideColumns(49, 59);
+    cs.hideColumns(69, 79);
+    List<int[]> hidden = cs.getHiddenColumns();
+    assertEquals(2, hidden.size());
+    assertEquals("[49, 59]", Arrays.toString(hidden.get(0)));
+    assertEquals("[69, 79]", Arrays.toString(hidden.get(1)));
+  
+    cs.hideColumns(48, 80);
+    hidden = cs.getHiddenColumns();
+    assertEquals(1, hidden.size());
+    assertEquals("[48, 80]", Arrays.toString(hidden.get(0)));
+
+    /*
+     * another...joining hidden ranges
+     */
+    cs = new ColumnSelection();
+    cs.hideColumns(10, 20);
+    cs.hideColumns(30, 40);
+    cs.hideColumns(50, 60);
+    // hiding 21-49 should merge to one range
+    cs.hideColumns(21, 49);
+    hidden = cs.getHiddenColumns();
+    assertEquals(1, hidden.size());
+    assertEquals("[10, 60]", Arrays.toString(hidden.get(0)));
+
+    /*
+     * another...lef overlap, subsumption, right overlap,
+     * no overlap of existing hidden ranges
+     */
+    cs = new ColumnSelection();
+    cs.hideColumns(10, 20);
+    cs.hideColumns(10, 20);
+    cs.hideColumns(30, 35);
+    cs.hideColumns(40, 50);
+    cs.hideColumns(60, 70);
+
+    cs.hideColumns(15, 45);
+    hidden = cs.getHiddenColumns();
+    assertEquals(2, hidden.size());
+    assertEquals("[10, 50]", Arrays.toString(hidden.get(0)));
+    assertEquals("[60, 70]", Arrays.toString(hidden.get(1)));
+  }
 }