Merge branch 'develop' into bug/JAL-2541cutRelocateFeatures
[jalview.git] / test / jalview / datamodel / features / SequenceFeaturesTest.java
index bb11a87..29e76bb 100644 (file)
@@ -2,18 +2,55 @@ package jalview.datamodel.features;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertSame;
 import static org.testng.Assert.assertTrue;
 
 import jalview.datamodel.SequenceFeature;
 
+import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.testng.annotations.Test;
 
+import junit.extensions.PA;
+
 public class SequenceFeaturesTest
 {
   @Test(groups = "Functional")
+  public void testConstructor()
+  {
+    SequenceFeaturesI store = new SequenceFeatures();
+    assertFalse(store.hasFeatures());
+
+    store = new SequenceFeatures((List<SequenceFeature>) null);
+    assertFalse(store.hasFeatures());
+
+    List<SequenceFeature> features = new ArrayList<>();
+    store = new SequenceFeatures(features);
+    assertFalse(store.hasFeatures());
+
+    SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
+            Float.NaN, null);
+    features.add(sf1);
+    SequenceFeature sf2 = new SequenceFeature("Metal", "desc", 15, 18,
+            Float.NaN, null);
+    features.add(sf2); // nested
+    SequenceFeature sf3 = new SequenceFeature("Pfam", "desc2", 0, 0,
+            Float.NaN, null); // non-positional
+    features.add(sf3);
+    store = new SequenceFeatures(features);
+    assertTrue(store.hasFeatures());
+    assertEquals(2, store.getFeatureCount(true)); // positional
+    assertEquals(1, store.getFeatureCount(false)); // non-positional
+    assertFalse(store.add(sf1)); // already contained
+    assertFalse(store.add(sf2)); // already contained
+    assertFalse(store.add(sf3)); // already contained
+  }
+
+  @Test(groups = "Functional")
   public void testGetPositionalFeatures()
   {
     SequenceFeaturesI store = new SequenceFeatures();
@@ -237,8 +274,6 @@ public class SequenceFeaturesTest
     SequenceFeature sf10 = addFeature(sf, "Cath", 40, 100);
     SequenceFeature sf11 = addFeature(sf, "Cath", 60, 100);
     SequenceFeature sf12 = addFeature(sf, "Cath", 70, 70);
-    // null type is weird but possible:
-    SequenceFeature sf13 = addFeature(sf, null, 5, 12);
   
     List<SequenceFeature> overlaps = sf.findFeatures(200, 200, "Pfam");
     assertTrue(overlaps.isEmpty());
@@ -281,8 +316,7 @@ public class SequenceFeaturesTest
     assertTrue(sf.findFeatures(0, 1000, "Metal").isEmpty());
 
     overlaps = sf.findFeatures(7, 7, (String) null);
-    assertEquals(overlaps.size(), 1);
-    assertTrue(overlaps.contains(sf13));
+    assertTrue(overlaps.isEmpty());
   }
 
   @Test(groups = "Functional")
@@ -334,6 +368,11 @@ public class SequenceFeaturesTest
     groups = sf.getFeatureGroups(false); // for non-positional
     assertEquals(groups.size(), 1);
     assertTrue(groups.contains("AGroup"));
+    groups = sf.getFeatureGroups(false, "AType");
+    assertEquals(groups.size(), 1);
+    assertTrue(groups.contains("AGroup"));
+    groups = sf.getFeatureGroups(true, "AnotherType");
+    assertTrue(groups.isEmpty());
 
     /*
      * add, then delete, more non-positional features of different types
@@ -508,13 +547,13 @@ public class SequenceFeaturesTest
     assertEquals(types.size(), 1);
     assertTrue(types.contains("Metal"));
 
-    // null type is possible...
+    // null type is rejected...
     SequenceFeature sf2 = new SequenceFeature(null, "desc", 10, 20,
             Float.NaN, null);
-    store.add(sf2);
+    assertFalse(store.add(sf2));
     types = store.getFeatureTypes();
-    assertEquals(types.size(), 2);
-    assertTrue(types.contains(null));
+    assertEquals(types.size(), 1);
+    assertFalse(types.contains(null));
     assertTrue(types.contains("Metal"));
 
     /*
@@ -524,7 +563,7 @@ public class SequenceFeaturesTest
             Float.NaN, null);
     store.add(sf3);
     types = store.getFeatureTypes();
-    assertEquals(types.size(), 3);
+    assertEquals(types.size(), 2);
     assertTrue(types.contains("Pfam"));
 
     /*
@@ -534,7 +573,7 @@ public class SequenceFeaturesTest
             10, 20, Float.NaN, null);
     store.add(sf4);
     types = store.getFeatureTypes();
-    assertEquals(types.size(), 4);
+    assertEquals(types.size(), 3);
     assertTrue(types.contains("Disulphide Bond"));
 
     /*
@@ -544,14 +583,14 @@ public class SequenceFeaturesTest
             Float.NaN, null);
     store.add(sf5);
     types = store.getFeatureTypes();
-    assertEquals(types.size(), 4); // unchanged
+    assertEquals(types.size(), 3); // unchanged
 
     /*
      * delete first Pfam - still have one
      */
     assertTrue(store.delete(sf3));
     types = store.getFeatureTypes();
-    assertEquals(types.size(), 4);
+    assertEquals(types.size(), 3);
     assertTrue(types.contains("Pfam"));
 
     /*
@@ -559,7 +598,7 @@ public class SequenceFeaturesTest
      */
     assertTrue(store.delete(sf5));
     types = store.getFeatureTypes();
-    assertEquals(types.size(), 3);
+    assertEquals(types.size(), 2);
     assertFalse(types.contains("Pfam"));
   }
 
@@ -580,12 +619,12 @@ public class SequenceFeaturesTest
     assertEquals(store.getFeatureCount(false), 0);
 
     /*
-     * another positional
+     * null feature type is rejected
      */
     SequenceFeature sf2 = new SequenceFeature(null, "desc", 10, 20,
             Float.NaN, null);
-    store.add(sf2);
-    assertEquals(store.getFeatureCount(true), 2);
+    assertFalse(store.add(sf2));
+    assertEquals(store.getFeatureCount(true), 1);
     assertEquals(store.getFeatureCount(false), 0);
   
     /*
@@ -594,7 +633,7 @@ public class SequenceFeaturesTest
     SequenceFeature sf3 = new SequenceFeature("Pfam", "desc", 0, 0,
             Float.NaN, null);
     store.add(sf3);
-    assertEquals(store.getFeatureCount(true), 2);
+    assertEquals(store.getFeatureCount(true), 1);
     assertEquals(store.getFeatureCount(false), 1);
   
     /*
@@ -603,39 +642,39 @@ public class SequenceFeaturesTest
     SequenceFeature sf4 = new SequenceFeature("Disulphide Bond", "desc",
             10, 20, Float.NaN, null);
     store.add(sf4);
-    assertEquals(store.getFeatureCount(true), 3);
+    assertEquals(store.getFeatureCount(true), 2);
     assertEquals(store.getFeatureCount(false), 1);
   
     /*
-     * add another Pfam
+     * add another Pfam but this time as a positional feature
      */
     SequenceFeature sf5 = new SequenceFeature("Pfam", "desc", 10, 20,
             Float.NaN, null);
     store.add(sf5);
-    assertEquals(store.getFeatureCount(true), 4);
-    assertEquals(store.getFeatureCount(false), 1);
-    assertEquals(store.getFeatureCount(true, "Pfam"), 1);
-    assertEquals(store.getFeatureCount(false, "Pfam"), 1);
+    assertEquals(store.getFeatureCount(true), 3); // sf1, sf4, sf5
+    assertEquals(store.getFeatureCount(false), 1); // sf3
+    assertEquals(store.getFeatureCount(true, "Pfam"), 1); // positional
+    assertEquals(store.getFeatureCount(false, "Pfam"), 1); // non-positional
     // search for type==null
-    assertEquals(store.getFeatureCount(true, (String) null), 1);
+    assertEquals(store.getFeatureCount(true, (String) null), 0);
     // search with no type specified
-    assertEquals(store.getFeatureCount(true, (String[]) null), 4);
+    assertEquals(store.getFeatureCount(true, (String[]) null), 3);
     assertEquals(store.getFeatureCount(true, "Metal", "Cath"), 1);
     assertEquals(store.getFeatureCount(true, "Disulphide Bond"), 1);
-    assertEquals(store.getFeatureCount(true, "Metal", "Pfam", null), 3);
+    assertEquals(store.getFeatureCount(true, "Metal", "Pfam", null), 2);
 
     /*
      * delete first Pfam (non-positional)
      */
     assertTrue(store.delete(sf3));
-    assertEquals(store.getFeatureCount(true), 4);
+    assertEquals(store.getFeatureCount(true), 3);
     assertEquals(store.getFeatureCount(false), 0);
   
     /*
      * delete second Pfam (positional)
      */
     assertTrue(store.delete(sf5));
-    assertEquals(store.getFeatureCount(true), 3);
+    assertEquals(store.getFeatureCount(true), 2);
     assertEquals(store.getFeatureCount(false), 0);
   }
 
@@ -653,7 +692,7 @@ public class SequenceFeaturesTest
     assertEquals(features.size(), 1);
     assertTrue(features.contains(sf1));
   
-    SequenceFeature sf2 = new SequenceFeature(null, "desc", 10, 20,
+    SequenceFeature sf2 = new SequenceFeature("Metallic", "desc", 10, 20,
             Float.NaN, null);
     store.add(sf2);
     features = store.getAllFeatures();
@@ -689,8 +728,14 @@ public class SequenceFeaturesTest
     features = store.getAllFeatures();
     assertEquals(features.size(), 5);
     assertTrue(features.contains(sf5));
+
+    /*
+     * select by type does not apply to non-positional features
+     */
     features = store.getAllFeatures("Cath");
-    assertTrue(features.isEmpty());
+    assertEquals(features.size(), 1);
+    assertTrue(features.contains(sf3));
+
     features = store.getAllFeatures("Pfam", "Cath", "Metal");
     assertEquals(features.size(), 3);
     assertTrue(features.contains(sf1));
@@ -724,6 +769,8 @@ public class SequenceFeaturesTest
             Float.NaN, null);
     assertTrue(store.add(sf1));
     assertEquals(store.getTotalFeatureLength(), 11);
+    assertEquals(store.getTotalFeatureLength("Metal"), 11);
+    assertEquals(store.getTotalFeatureLength("Plastic"), 0);
 
     // re-add does nothing!
     assertFalse(store.add(sf1));
@@ -830,4 +877,402 @@ public class SequenceFeaturesTest
     assertEquals(sf.getMinimumScore("Metal", true), Float.NaN);
     assertEquals(sf.getMaximumScore("Metal", true), Float.NaN);
   }
+
+  @Test(groups = "Functional")
+  public void testVarargsToTypes()
+  {
+    SequenceFeatures sf = new SequenceFeatures();
+    sf.add(new SequenceFeature("Metal", "desc", 0, 0, Float.NaN, "group"));
+    sf.add(new SequenceFeature("Cath", "desc", 10, 20, Float.NaN, "group"));
+
+    /*
+     * no type specified - get all types stored
+     * they are returned in keyset (alphabetical) order
+     */
+    Map<String, FeatureStore> featureStores = (Map<String, FeatureStore>) PA
+            .getValue(sf, "featureStore");
+
+    Iterable<FeatureStore> types = sf.varargToTypes();
+    Iterator<FeatureStore> iterator = types.iterator();
+    assertTrue(iterator.hasNext());
+    assertSame(iterator.next(), featureStores.get("Cath"));
+    assertTrue(iterator.hasNext());
+    assertSame(iterator.next(), featureStores.get("Metal"));
+    assertFalse(iterator.hasNext());
+
+    /*
+     * empty array is the same as no vararg parameter supplied
+     * so treated as all stored types
+     */
+    types = sf.varargToTypes(new String[] {});
+    iterator = types.iterator();
+    assertTrue(iterator.hasNext());
+    assertSame(iterator.next(), featureStores.get("Cath"));
+    assertTrue(iterator.hasNext());
+    assertSame(iterator.next(), featureStores.get("Metal"));
+    assertFalse(iterator.hasNext());
+
+    /*
+     * null type specified; this is passed as vararg
+     * String[1] {null}
+     */
+    types = sf.varargToTypes((String) null);
+    assertFalse(types.iterator().hasNext());
+
+    /*
+     * null types array specified; this is passed as vararg null
+     */
+    types = sf.varargToTypes((String[]) null);
+    iterator = types.iterator();
+    assertTrue(iterator.hasNext());
+    assertSame(iterator.next(), featureStores.get("Cath"));
+    assertTrue(iterator.hasNext());
+    assertSame(iterator.next(), featureStores.get("Metal"));
+    assertFalse(iterator.hasNext());
+
+    /*
+     * one type specified
+     */
+    types = sf.varargToTypes("Metal");
+    iterator = types.iterator();
+    assertTrue(iterator.hasNext());
+    assertSame(iterator.next(), featureStores.get("Metal"));
+    assertFalse(iterator.hasNext());
+
+    /*
+     * two types specified - get sorted alphabetically
+     */
+    types = sf.varargToTypes("Metal", "Cath");
+    iterator = types.iterator();
+    assertTrue(iterator.hasNext());
+    assertSame(iterator.next(), featureStores.get("Cath"));
+    assertTrue(iterator.hasNext());
+    assertSame(iterator.next(), featureStores.get("Metal"));
+    assertFalse(iterator.hasNext());
+
+    /*
+     * null type included - should be ignored
+     */
+    types = sf.varargToTypes("Metal", null, "Helix");
+    iterator = types.iterator();
+    assertTrue(iterator.hasNext());
+    assertSame(iterator.next(), featureStores.get("Metal"));
+    assertFalse(iterator.hasNext());
+  }
+
+  @Test(groups = "Functional")
+  public void testGetFeatureTypes_byOntology()
+  {
+    SequenceFeaturesI store = new SequenceFeatures();
+  
+    SequenceFeature sf1 = new SequenceFeature("transcript", "desc", 10, 20,
+            Float.NaN, null);
+    store.add(sf1);
+    // mRNA isA mature_transcript isA transcript
+    SequenceFeature sf2 = new SequenceFeature("mRNA", "desc", 10, 20,
+            Float.NaN, null);
+    store.add(sf2);
+    // just to prove non-positional feature types are included
+    SequenceFeature sf3 = new SequenceFeature("mRNA", "desc", 0, 0,
+            Float.NaN, null);
+    store.add(sf3);
+    SequenceFeature sf4 = new SequenceFeature("CDS", "desc", 0, 0,
+            Float.NaN, null);
+    store.add(sf4);
+
+    Set<String> types = store.getFeatureTypes("transcript");
+    assertEquals(types.size(), 2);
+    assertTrue(types.contains("transcript"));
+    assertTrue(types.contains("mRNA"));
+
+    // matches include arguments whether SO terms or not
+    types = store.getFeatureTypes("transcript", "CDS");
+    assertEquals(types.size(), 3);
+    assertTrue(types.contains("transcript"));
+    assertTrue(types.contains("mRNA"));
+    assertTrue(types.contains("CDS"));
+
+    types = store.getFeatureTypes("exon");
+    assertTrue(types.isEmpty());
+  }
+
+  @Test(groups = "Functional")
+  public void testGetFeaturesByOntology()
+  {
+    SequenceFeaturesI store = new SequenceFeatures();
+    List<SequenceFeature> features = store.getFeaturesByOntology();
+    assertTrue(features.isEmpty());
+    assertTrue(store.getFeaturesByOntology(new String[] {}).isEmpty());
+    assertTrue(store.getFeaturesByOntology((String[]) null).isEmpty());
+  
+    SequenceFeature transcriptFeature = new SequenceFeature("transcript", "desc", 10, 20,
+            Float.NaN, null);
+    store.add(transcriptFeature);
+
+    /*
+     * mRNA is a sub-type of transcript; added here 'as if' non-positional
+     * just to show that non-positional features are included in results
+     */
+    SequenceFeature mrnaFeature = new SequenceFeature("mRNA", "desc", 0, 0,
+            Float.NaN, null);
+    store.add(mrnaFeature);
+
+    SequenceFeature pfamFeature = new SequenceFeature("Pfam", "desc", 30, 40,
+            Float.NaN, null);
+    store.add(pfamFeature);
+
+    /*
+     * "transcript" matches both itself and the sub-term "mRNA"
+     */
+    features = store.getFeaturesByOntology("transcript");
+    assertEquals(features.size(), 2);
+    assertTrue(features.contains(transcriptFeature));
+    assertTrue(features.contains(mrnaFeature));
+
+    /*
+     * "mRNA" matches itself but not parent term "transcript"
+     */
+    features = store.getFeaturesByOntology("mRNA");
+    assertEquals(features.size(), 1);
+    assertTrue(features.contains(mrnaFeature));
+
+    /*
+     * "pfam" is not an SO term but is included as an exact match
+     */
+    features = store.getFeaturesByOntology("mRNA", "Pfam");
+    assertEquals(features.size(), 2);
+    assertTrue(features.contains(mrnaFeature));
+    assertTrue(features.contains(pfamFeature));
+
+    features = store.getFeaturesByOntology("sequence_variant");
+    assertTrue(features.isEmpty());
+  }
+
+  @Test(groups = "Functional")
+  public void testSortFeatures()
+  {
+    List<SequenceFeature> sfs = new ArrayList<>();
+    SequenceFeature sf1 = new SequenceFeature("Pfam", "desc", 30, 80,
+            Float.NaN, null);
+    sfs.add(sf1);
+    SequenceFeature sf2 = new SequenceFeature("Rfam", "desc", 40, 50,
+            Float.NaN, null);
+    sfs.add(sf2);
+    SequenceFeature sf3 = new SequenceFeature("Rfam", "desc", 50, 60,
+            Float.NaN, null);
+    sfs.add(sf3);
+
+    // sort by end position descending
+    SequenceFeatures.sortFeatures(sfs, false);
+    assertSame(sfs.get(0), sf1);
+    assertSame(sfs.get(1), sf3);
+    assertSame(sfs.get(2), sf2);
+
+    // sort by start position ascending
+    SequenceFeatures.sortFeatures(sfs, true);
+    assertSame(sfs.get(0), sf1);
+    assertSame(sfs.get(1), sf2);
+    assertSame(sfs.get(2), sf3);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetFeaturesForGroup()
+  {
+    SequenceFeaturesI store = new SequenceFeatures();
+
+    List<SequenceFeature> features = store.getFeaturesForGroup(true, null);
+    assertTrue(features.isEmpty());
+    assertTrue(store.getFeaturesForGroup(false, null).isEmpty());
+    assertTrue(store.getFeaturesForGroup(true, "Uniprot").isEmpty());
+    assertTrue(store.getFeaturesForGroup(false, "Uniprot").isEmpty());
+
+    SequenceFeature sf1 = new SequenceFeature("Pfam", "desc", 4, 10, 0f,
+            null);
+    SequenceFeature sf2 = new SequenceFeature("Pfam", "desc", 0, 0, 0f,
+            null);
+    SequenceFeature sf3 = new SequenceFeature("Pfam", "desc", 4, 10, 0f,
+            "Uniprot");
+    SequenceFeature sf4 = new SequenceFeature("Metal", "desc", 0, 0, 0f,
+            "Rfam");
+    SequenceFeature sf5 = new SequenceFeature("Cath", "desc", 5, 15, 0f,
+            null);
+    store.add(sf1);
+    store.add(sf2);
+    store.add(sf3);
+    store.add(sf4);
+    store.add(sf5);
+
+    // positional features for null group, any type
+    features = store.getFeaturesForGroup(true, null);
+    assertEquals(features.size(), 2);
+    assertTrue(features.contains(sf1));
+    assertTrue(features.contains(sf5));
+
+    // positional features for null group, specified type
+    features = store.getFeaturesForGroup(true, null, new String[] { "Pfam",
+        "Xfam" });
+    assertEquals(features.size(), 1);
+    assertTrue(features.contains(sf1));
+    features = store.getFeaturesForGroup(true, null, new String[] { "Pfam",
+        "Xfam", "Cath" });
+    assertEquals(features.size(), 2);
+    assertTrue(features.contains(sf1));
+    assertTrue(features.contains(sf5));
+
+    // positional features for non-null group, any type
+    features = store.getFeaturesForGroup(true, "Uniprot");
+    assertEquals(features.size(), 1);
+    assertTrue(features.contains(sf3));
+    assertTrue(store.getFeaturesForGroup(true, "Rfam").isEmpty());
+
+    // positional features for non-null group, specified type
+    features = store.getFeaturesForGroup(true, "Uniprot", "Pfam", "Xfam",
+            "Rfam");
+    assertEquals(features.size(), 1);
+    assertTrue(features.contains(sf3));
+    assertTrue(store.getFeaturesForGroup(true, "Uniprot", "Cath").isEmpty());
+
+    // non-positional features for null group, any type
+    features = store.getFeaturesForGroup(false, null);
+    assertEquals(features.size(), 1);
+    assertTrue(features.contains(sf2));
+
+    // non-positional features for null group, specified type
+    features = store.getFeaturesForGroup(false, null, "Pfam", "Xfam");
+    assertEquals(features.size(), 1);
+    assertTrue(features.contains(sf2));
+    assertTrue(store.getFeaturesForGroup(false, null, "Cath").isEmpty());
+
+    // non-positional features for non-null group, any type
+    features = store.getFeaturesForGroup(false, "Rfam");
+    assertEquals(features.size(), 1);
+    assertTrue(features.contains(sf4));
+    assertTrue(store.getFeaturesForGroup(false, "Uniprot").isEmpty());
+
+    // non-positional features for non-null group, specified type
+    features = store.getFeaturesForGroup(false, "Rfam", "Pfam", "Metal");
+    assertEquals(features.size(), 1);
+    assertTrue(features.contains(sf4));
+    assertTrue(store.getFeaturesForGroup(false, "Rfam", "Cath", "Pfam")
+            .isEmpty());
+  }
+
+  @Test(groups = "Functional")
+  public void testShiftFeatures()
+  {
+    SequenceFeatures store = new SequenceFeatures();
+    assertFalse(store.shiftFeatures(0, 1));
+
+    SequenceFeature sf1 = new SequenceFeature("Cath", "", 2, 5, 0f, null);
+    store.add(sf1);
+    // nested feature:
+    SequenceFeature sf2 = new SequenceFeature("Metal", "", 8, 14, 0f, null);
+    store.add(sf2);
+    // contact feature:
+    SequenceFeature sf3 = new SequenceFeature("Disulfide bond", "", 23, 32,
+            0f, null);
+    store.add(sf3);
+    // non-positional feature:
+    SequenceFeature sf4 = new SequenceFeature("Pfam", "", 0, 0, 0f, null);
+    store.add(sf4);
+  
+    /*
+     * shift features right by 5
+     */
+    assertTrue(store.shiftFeatures(0, 5));
+  
+    // non-positional features untouched:
+    List<SequenceFeature> nonPos = store.getNonPositionalFeatures();
+    assertEquals(nonPos.size(), 1);
+    assertTrue(nonPos.contains(sf4));
+  
+    // positional features are replaced
+    List<SequenceFeature> pos = store.getPositionalFeatures();
+    assertEquals(pos.size(), 3);
+    assertFalse(pos.contains(sf1));
+    assertFalse(pos.contains(sf2));
+    assertFalse(pos.contains(sf3));
+    SequenceFeatures.sortFeatures(pos, true); // ascending start pos
+    assertEquals(pos.get(0).getBegin(), 7);
+    assertEquals(pos.get(0).getEnd(), 10);
+    assertEquals(pos.get(0).getType(), "Cath");
+    assertEquals(pos.get(1).getBegin(), 13);
+    assertEquals(pos.get(1).getEnd(), 19);
+    assertEquals(pos.get(1).getType(), "Metal");
+    assertEquals(pos.get(2).getBegin(), 28);
+    assertEquals(pos.get(2).getEnd(), 37);
+    assertEquals(pos.get(2).getType(), "Disulfide bond");
+  
+    /*
+     * now shift left by 15
+     * feature at [7-10] should be removed
+     * feature at [13-19] should become [1-4] 
+     */
+    assertTrue(store.shiftFeatures(0, -15));
+    pos = store.getPositionalFeatures();
+    assertEquals(pos.size(), 2);
+    SequenceFeatures.sortFeatures(pos, true);
+    assertEquals(pos.get(0).getBegin(), 1);
+    assertEquals(pos.get(0).getEnd(), 4);
+    assertEquals(pos.get(0).getType(), "Metal");
+    assertEquals(pos.get(1).getBegin(), 13);
+    assertEquals(pos.get(1).getEnd(), 22);
+    assertEquals(pos.get(1).getType(), "Disulfide bond");
+
+    /*
+     * shift right by 4 from column 2
+     * feature at [1-4] should be unchanged
+     * feature at [13-22] should become [17-26] 
+     */
+    assertTrue(store.shiftFeatures(2, 4));
+    pos = store.getPositionalFeatures();
+    assertEquals(pos.size(), 2);
+    SequenceFeatures.sortFeatures(pos, true);
+    assertEquals(pos.get(0).getBegin(), 1);
+    assertEquals(pos.get(0).getEnd(), 4);
+    assertEquals(pos.get(0).getType(), "Metal");
+    assertEquals(pos.get(1).getBegin(), 17);
+    assertEquals(pos.get(1).getEnd(), 26);
+    assertEquals(pos.get(1).getType(), "Disulfide bond");
+
+    /*
+     * shift right from column 18
+     * should be no updates
+     */
+    SequenceFeature f1 = pos.get(0);
+    SequenceFeature f2 = pos.get(1);
+    assertFalse(store.shiftFeatures(18, 6));
+    pos = store.getPositionalFeatures();
+    assertEquals(pos.size(), 2);
+    SequenceFeatures.sortFeatures(pos, true);
+    assertSame(pos.get(0), f1);
+    assertSame(pos.get(1), f2);
+  }
+
+  @Test(groups = "Functional")
+  public void testIsOntologyTerm()
+  {
+    SequenceFeatures store = new SequenceFeatures();
+    assertTrue(store.isOntologyTerm("gobbledygook"));
+    assertTrue(store.isOntologyTerm("transcript", "transcript"));
+    assertTrue(store.isOntologyTerm("mRNA", "transcript"));
+    assertFalse(store.isOntologyTerm("transcript", "mRNA"));
+    assertTrue(store.isOntologyTerm("junk", "transcript", "junk"));
+    assertTrue(store.isOntologyTerm("junk", new String[] {}));
+    assertTrue(store.isOntologyTerm("junk", (String[]) null));
+  }
+
+  @Test(groups = "Functional")
+  public void testDeleteAll()
+  {
+    SequenceFeaturesI store = new SequenceFeatures();
+    assertFalse(store.hasFeatures());
+    store.deleteAll();
+    assertFalse(store.hasFeatures());
+    store.add(new SequenceFeature("Cath", "Desc", 12, 20, 0f, "Group"));
+    store.add(new SequenceFeature("Pfam", "Desc", 6, 12, 2f, "Group2"));
+    assertTrue(store.hasFeatures());
+    store.deleteAll();
+    assertFalse(store.hasFeatures());
+  }
 }