Merge branch 'develop' into features/JAL-2446NCList
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 1 Aug 2017 09:57:49 +0000 (11:57 +0200)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 1 Aug 2017 09:57:49 +0000 (11:57 +0200)
1  2 
src/jalview/appletgui/FeatureSettings.java
src/jalview/gui/IdPanel.java
test/jalview/io/FeaturesFileTest.java

@@@ -23,7 -23,7 +23,7 @@@ package jalview.appletgui
  import jalview.api.FeatureColourI;
  import jalview.api.FeatureSettingsControllerI;
  import jalview.datamodel.AlignmentI;
 -import jalview.datamodel.SequenceFeature;
 +import jalview.datamodel.SequenceI;
  import jalview.util.MessageManager;
  
  import java.awt.BorderLayout;
@@@ -56,12 -56,13 +56,12 @@@ import java.awt.event.MouseListener
  import java.awt.event.MouseMotionListener;
  import java.awt.event.WindowAdapter;
  import java.awt.event.WindowEvent;
 +import java.util.ArrayList;
  import java.util.Arrays;
 -import java.util.Enumeration;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
 -import java.util.Vector;
  
  public class FeatureSettings extends Panel implements ItemListener,
          MouseListener, MouseMotionListener, ActionListener,
    // Group selection states
    void resetTable(boolean groupsChanged)
    {
 -    SequenceFeature[] tmpfeatures;
 -    String group = null, type;
 -    Vector<String> visibleChecks = new Vector<String>();
 +    List<String> displayableTypes = new ArrayList<String>();
      Set<String> foundGroups = new HashSet<String>();
 +
      AlignmentI alignment = av.getAlignment();
  
      for (int i = 0; i < alignment.getHeight(); i++)
      {
 -      if (alignment.getSequenceAt(i).getSequenceFeatures() == null)
 -      {
 -        continue;
 -      }
 +      SequenceI seq = alignment.getSequenceAt(i);
  
 -      tmpfeatures = alignment.getSequenceAt(i).getSequenceFeatures();
 -      int index = 0;
 -      while (index < tmpfeatures.length)
 +      /*
 +       * get the sequence's groups for positional features
 +       * and keep track of which groups are visible
 +       */
 +      Set<String> groups = seq.getFeatures().getFeatureGroups(true);
 +      Set<String> visibleGroups = new HashSet<String>();
 +      for (String group : groups)
        {
 -        group = tmpfeatures[index].featureGroup;
 -        foundGroups.add(group);
 -
 +        // if (group == null || fr.checkGroupVisibility(group, true))
          if (group == null || checkGroupState(group))
          {
 -          type = tmpfeatures[index].getType();
 -          if (!visibleChecks.contains(type))
 -          {
 -            visibleChecks.addElement(type);
 -          }
 +          visibleGroups.add(group);
          }
 -        index++;
        }
 +      foundGroups.addAll(groups);
 +
 +      /*
 +       * get distinct feature types for visible groups
 +       * record distinct visible types
 +       */
 +      Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
 +              visibleGroups.toArray(new String[visibleGroups.size()]));
 +      displayableTypes.addAll(types);
      }
  
      /*
      {
        comps = featurePanel.getComponents();
        check = (MyCheckbox) comps[i];
 -      if (!visibleChecks.contains(check.type))
 +      if (!displayableTypes.contains(check.type))
        {
          featurePanel.remove(i);
          cSize--;
        {
          String item = rol.get(ro);
  
 -        if (!visibleChecks.contains(item))
 +        if (!displayableTypes.contains(item))
          {
            continue;
          }
  
 -        visibleChecks.removeElement(item);
 +        displayableTypes.remove(item);
  
          addCheck(false, item);
        }
      }
  
 -    // now add checkboxes which should be visible,
 -    // if they have not already been added
 -    Enumeration<String> en = visibleChecks.elements();
 -
 -    while (en.hasMoreElements())
 +    /*
 +     * now add checkboxes which should be visible,
 +     * if they have not already been added
 +     */
 +    for (String type : displayableTypes)
      {
 -      addCheck(groupsChanged, en.nextElement().toString());
 +      addCheck(groupsChanged, type);
      }
  
      featurePanel.setLayout(new GridLayout(featurePanel.getComponentCount(),
        Checkbox check = (Checkbox) featurePanel.getComponent(i);
        check.setState(!check.getState());
      }
-     selectionChanged();
+     selectionChanged(true);
    }
  
    private ItemListener groupItemListener = new ItemListener()
    @Override
    public void itemStateChanged(ItemEvent evt)
    {
-     selectionChanged();
+     selectionChanged(true);
    }
  
-   void selectionChanged()
+   void selectionChanged(boolean updateOverview)
    {
      Component[] comps = featurePanel.getComponents();
      int cSize = comps.length;
  
      fr.setFeaturePriority(data);
  
-     ap.paintAlignment(true);
+     ap.paintAlignment(updateOverview);
    }
  
    MyCheckbox selectedCheck;
@@@ -154,7 -154,7 +154,7 @@@ public class IdPanel extends JPanel imp
        {
          av.getRanges().scrollRight(true);
        }
-       else
+       else if (!av.getWrapAlignment())
        {
          av.getRanges().scrollUp(false);
        }
        {
          av.getRanges().scrollRight(false);
        }
-       else
+       else if (!av.getWrapAlignment())
        {
          av.getRanges().scrollUp(true);
        }
    {
      int seq2 = alignPanel.getSeqPanel().findSeq(e);
      Sequence sq = (Sequence) av.getAlignment().getSequenceAt(seq2);
 -    // build a new links menu based on the current links + any non-positional
 -    // features
 +
 +    /*
 +     *  build a new links menu based on the current links
 +     *  and any non-positional features
 +     */
      List<String> nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
 -    SequenceFeature sfs[] = sq == null ? null : sq.getSequenceFeatures();
 -    if (sfs != null)
 +    for (SequenceFeature sf : sq.getFeatures().getNonPositionalFeatures())
      {
 -      for (SequenceFeature sf : sfs)
 +      if (sf.links != null)
        {
 -        if (sf.begin == sf.end && sf.begin == 0)
 +        for (String link : sf.links)
          {
 -          if (sf.links != null && sf.links.size() > 0)
 -          {
 -            for (int l = 0, lSize = sf.links.size(); l < lSize; l++)
 -            {
 -              nlinks.add(sf.links.elementAt(l));
 -            }
 -          }
 +          nlinks.add(link);
          }
        }
      }
@@@ -23,6 -23,7 +23,6 @@@ package jalview.io
  import static org.testng.AssertJUnit.assertEquals;
  import static org.testng.AssertJUnit.assertFalse;
  import static org.testng.AssertJUnit.assertNotNull;
 -import static org.testng.AssertJUnit.assertNull;
  import static org.testng.AssertJUnit.assertTrue;
  
  import jalview.api.FeatureColourI;
@@@ -32,17 -33,12 +32,17 @@@ import jalview.datamodel.AlignmentI
  import jalview.datamodel.SequenceDummy;
  import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceI;
 +import jalview.datamodel.features.SequenceFeatures;
  import jalview.gui.AlignFrame;
  import jalview.gui.JvOptionPane;
  
  import java.awt.Color;
  import java.io.File;
  import java.io.IOException;
 +import java.util.ArrayList;
 +import java.util.Arrays;
 +import java.util.HashMap;
 +import java.util.List;
  import java.util.Map;
  
  import org.testng.annotations.BeforeClass;
@@@ -79,22 -75,21 +79,26 @@@ public class FeaturesFileTes
       * updated - JAL-1904), and verify (some) feature group colours
       */
      colours = af.getFeatureRenderer().getFeatureColours();
-     assertEquals("26 feature group colours not found", 26, colours.size());
+     assertEquals("27 feature group colours not found", 27, colours.size());
      assertEquals(colours.get("Cath").getColour(), new Color(0x93b1d1));
      assertEquals(colours.get("ASX-MOTIF").getColour(), new Color(0x6addbb));
+     FeatureColourI kdColour = colours.get("kdHydrophobicity");
+     assertTrue(kdColour.isGraduatedColour());
+     assertTrue(kdColour.isAboveThreshold());
+     assertEquals(-2f, kdColour.getThreshold());
  
      /*
       * verify (some) features on sequences
       */
 -    SequenceFeature[] sfs = al.getSequenceAt(0).getDatasetSequence()
 +    List<SequenceFeature> sfs = al.getSequenceAt(0).getDatasetSequence()
              .getSequenceFeatures(); // FER_CAPAA
 -    assertEquals(8, sfs.length);
 -    SequenceFeature sf = sfs[0];
 +    SequenceFeatures.sortFeatures(sfs, true);
 +    assertEquals(8, sfs.size());
 +
 +    /*
 +     * verify (in ascending start position order)
 +     */
 +    SequenceFeature sf = sfs.get(0);
      assertEquals("Pfam family%LINK%", sf.description);
      assertEquals(0, sf.begin);
      assertEquals(0, sf.end);
      assertEquals("Pfam family|http://pfam.xfam.org/family/PF00111",
              sf.links.get(0));
  
 -    sf = sfs[1];
 +    sf = sfs.get(1);
 +    assertEquals("Ferredoxin_fold Status: True Positive ", sf.description);
 +    assertEquals(3, sf.begin);
 +    assertEquals(93, sf.end);
 +    assertEquals("uniprot", sf.featureGroup);
 +    assertEquals("Cath", sf.type);
 +
 +    sf = sfs.get(2);
 +    assertEquals("Fer2 Status: True Positive Pfam 8_8%LINK%",
 +            sf.description);
 +    assertEquals("Pfam 8_8|http://pfam.xfam.org/family/PF00111",
 +            sf.links.get(0));
 +    assertEquals(8, sf.begin);
 +    assertEquals(83, sf.end);
 +    assertEquals("uniprot", sf.featureGroup);
 +    assertEquals("Pfam", sf.type);
 +
 +    sf = sfs.get(3);
      assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
      assertEquals(39, sf.begin);
      assertEquals(39, sf.end);
      assertEquals("uniprot", sf.featureGroup);
      assertEquals("METAL", sf.type);
 -    sf = sfs[2];
 +
 +    sf = sfs.get(4);
      assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
      assertEquals(44, sf.begin);
      assertEquals(44, sf.end);
      assertEquals("uniprot", sf.featureGroup);
      assertEquals("METAL", sf.type);
 -    sf = sfs[3];
 +
 +    sf = sfs.get(5);
      assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
      assertEquals(47, sf.begin);
      assertEquals(47, sf.end);
      assertEquals("uniprot", sf.featureGroup);
      assertEquals("METAL", sf.type);
 -    sf = sfs[4];
 +
 +    sf = sfs.get(6);
      assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
      assertEquals(77, sf.begin);
      assertEquals(77, sf.end);
      assertEquals("uniprot", sf.featureGroup);
      assertEquals("METAL", sf.type);
 -    sf = sfs[5];
 -    assertEquals("Fer2 Status: True Positive Pfam 8_8%LINK%",
 -            sf.description);
 -    assertEquals("Pfam 8_8|http://pfam.xfam.org/family/PF00111",
 -            sf.links.get(0));
 -    assertEquals(8, sf.begin);
 -    assertEquals(83, sf.end);
 -    assertEquals("uniprot", sf.featureGroup);
 -    assertEquals("Pfam", sf.type);
 -    sf = sfs[6];
 -    assertEquals("Ferredoxin_fold Status: True Positive ", sf.description);
 -    assertEquals(3, sf.begin);
 -    assertEquals(93, sf.end);
 -    assertEquals("uniprot", sf.featureGroup);
 -    assertEquals("Cath", sf.type);
 -    sf = sfs[7];
 +
 +    sf = sfs.get(7);
      assertEquals(
              "High confidence server. Only hits with scores over 0.8 are reported. PHOSPHORYLATION (T) 89_8%LINK%",
              sf.description);
      assertEquals(colours.get("METAL").getColour(), new Color(0xcc9900));
  
      // verify feature on FER_CAPAA
 -    SequenceFeature[] sfs = al.getSequenceAt(0).getDatasetSequence()
 +    List<SequenceFeature> sfs = al.getSequenceAt(0).getDatasetSequence()
              .getSequenceFeatures();
 -    assertEquals(1, sfs.length);
 -    SequenceFeature sf = sfs[0];
 +    assertEquals(1, sfs.size());
 +    SequenceFeature sf = sfs.get(0);
      assertEquals("Iron-sulfur,2Fe-2S", sf.description);
      assertEquals(44, sf.begin);
      assertEquals(45, sf.end);
  
      // verify feature on FER1_SOLLC
      sfs = al.getSequenceAt(2).getDatasetSequence().getSequenceFeatures();
 -    assertEquals(1, sfs.length);
 -    sf = sfs[0];
 +    assertEquals(1, sfs.size());
 +    sf = sfs.get(0);
      assertEquals("uniprot", sf.description);
      assertEquals(55, sf.begin);
      assertEquals(130, sf.end);
              featuresFile.parse(al.getDataset(), colours, true));
  
      // verify feature on FER_CAPAA
 -    SequenceFeature[] sfs = al.getSequenceAt(0).getDatasetSequence()
 +    List<SequenceFeature> sfs = al.getSequenceAt(0).getDatasetSequence()
              .getSequenceFeatures();
 -    assertEquals(1, sfs.length);
 -    SequenceFeature sf = sfs[0];
 +    assertEquals(1, sfs.size());
 +    SequenceFeature sf = sfs.get(0);
      // description parsed from Note attribute
      assertEquals("Iron-sulfur (2Fe-2S),another note", sf.description);
      assertEquals(39, sf.begin);
  
      // verify feature on FER1_SOLLC1
      sfs = al.getSequenceAt(2).getDatasetSequence().getSequenceFeatures();
 -    assertEquals(1, sfs.length);
 -    sf = sfs[0];
 +    assertEquals(1, sfs.size());
 +    sf = sfs.get(0);
      // ID used for description if available
      assertEquals("$23", sf.description);
      assertEquals(55, sf.begin);
              featuresFile.parse(al.getDataset(), colours, true));
  
      // verify FER_CAPAA feature
 -    SequenceFeature[] sfs = al.getSequenceAt(0).getDatasetSequence()
 +    List<SequenceFeature> sfs = al.getSequenceAt(0).getDatasetSequence()
              .getSequenceFeatures();
 -    assertEquals(1, sfs.length);
 -    SequenceFeature sf = sfs[0];
 +    assertEquals(1, sfs.size());
 +    SequenceFeature sf = sfs.get(0);
      assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
      assertEquals(39, sf.begin);
      assertEquals(39, sf.end);
  
      // verify FER1_SOLLC feature
      sfs = al.getSequenceAt(2).getDatasetSequence().getSequenceFeatures();
 -    assertEquals(1, sfs.length);
 -    sf = sfs[0];
 +    assertEquals(1, sfs.size());
 +    sf = sfs.get(0);
      assertEquals("Iron-phosphorus (2Fe-P)", sf.description);
      assertEquals(86, sf.begin);
      assertEquals(87, sf.end);
      assertFalse("dummy replacement buggy for seq2",
              placeholderseq.equals(seq2.getSequenceAsString()));
      assertNotNull("No features added to seq1", seq1.getSequenceFeatures());
 -    assertEquals("Wrong number of features", 3,
 -            seq1.getSequenceFeatures().length);
 -    assertNull(seq2.getSequenceFeatures());
 +    assertEquals("Wrong number of features", 3, seq1.getSequenceFeatures()
 +            .size());
 +    assertTrue(seq2.getSequenceFeatures().isEmpty());
      assertEquals(
              "Wrong number of features",
              0,
              seq2.getSequenceFeatures() == null ? 0 : seq2
 -                    .getSequenceFeatures().length);
 +                    .getSequenceFeatures().size());
      assertTrue(
              "Expected at least one CDNA/Protein mapping for seq1",
              dataset.getCodonFrame(seq1) != null
              + "GAMMA-TURN\tred|0,255,255|20.0|95.0|below|66.0\n"
              + "Pfam\tred\n"
              + "STARTGROUP\tuniprot\n"
 +            + "Cath\tFER_CAPAA\t-1\t0\t0\tDomain\n" // non-positional feature
              + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\n"
              + "Turn\tFER_CAPAA\t-1\t36\t38\tGAMMA-TURN\n"
              + "<html>Pfam domain<a href=\"http://pfam.xfam.org/family/PF00111\">Pfam_3_4</a></html>\tFER_CAPAA\t-1\t20\t20\tPfam\n"
      featuresFile.parse(al.getDataset(), colours, false);
  
      /*
 -     * first with no features displayed
 +     * add positional and non-positional features with null and
 +     * empty feature group to check handled correctly
 +     */
 +    SequenceI seq = al.getSequenceAt(1); // FER_CAPAN
 +    seq.addSequenceFeature(new SequenceFeature("Pfam", "desc1", 0, 0, 1.3f,
 +            null));
 +    seq.addSequenceFeature(new SequenceFeature("Pfam", "desc2", 4, 9,
 +            Float.NaN, null));
 +    seq = al.getSequenceAt(2); // FER1_SOLLC
 +    seq.addSequenceFeature(new SequenceFeature("Pfam", "desc3", 0, 0,
 +            Float.NaN, ""));
 +    seq.addSequenceFeature(new SequenceFeature("Pfam", "desc4", 5, 8,
 +            -2.6f, ""));
 +
 +    /*
 +     * first with no features displayed, exclude non-positional features
       */
      FeatureRenderer fr = af.alignPanel.getFeatureRenderer();
      Map<String, FeatureColourI> visible = fr.getDisplayedFeatureCols();
 +    List<String> visibleGroups = new ArrayList<String>(
 +            Arrays.asList(new String[] {}));
      String exported = featuresFile.printJalviewFormat(
 -            al.getSequencesArray(), visible);
 +            al.getSequencesArray(), visible, visibleGroups, false);
      String expected = "No Features Visible";
      assertEquals(expected, exported);
  
      /*
 +     * include non-positional features
 +     */
 +    visibleGroups.add("uniprot");
 +    exported = featuresFile.printJalviewFormat(al.getSequencesArray(),
 +            visible, visibleGroups, true);
 +    expected = "Cath\tFER_CAPAA\t-1\t0\t0\tDomain\t0.0\n"
 +            + "desc1\tFER_CAPAN\t-1\t0\t0\tPfam\t1.3\n"
 +            + "desc3\tFER1_SOLLC\t-1\t0\t0\tPfam\n" // NaN is not output
 +            + "\nSTARTGROUP\tuniprot\nENDGROUP\tuniprot\n";
 +    assertEquals(expected, exported);
 +
 +    /*
       * set METAL (in uniprot group) and GAMMA-TURN visible, but not Pfam
       */
      fr.setVisible("METAL");
      fr.setVisible("GAMMA-TURN");
      visible = fr.getDisplayedFeatureCols();
      exported = featuresFile.printJalviewFormat(al.getSequencesArray(),
 -            visible);
 +            visible, visibleGroups, false);
      expected = "METAL\tcc9900\n"
              + "GAMMA-TURN\tff0000|00ffff|20.0|95.0|below|66.0\n"
              + "\nSTARTGROUP\tuniprot\n"
 -            + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\t0.0\n"
              + "Turn\tFER_CAPAA\t-1\t36\t38\tGAMMA-TURN\t0.0\n"
 +            + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\t0.0\n"
              + "ENDGROUP\tuniprot\n";
      assertEquals(expected, exported);
  
      fr.setVisible("Pfam");
      visible = fr.getDisplayedFeatureCols();
      exported = featuresFile.printJalviewFormat(al.getSequencesArray(),
 -            visible);
 +            visible, visibleGroups, false);
      /*
 -     * note the order of feature types is uncontrolled - derives from
 -     * FeaturesDisplayed.featuresDisplayed which is a HashSet
 +     * features are output within group, ordered by sequence and by type
       */
      expected = "METAL\tcc9900\n"
              + "Pfam\tff0000\n"
              + "GAMMA-TURN\tff0000|00ffff|20.0|95.0|below|66.0\n"
              + "\nSTARTGROUP\tuniprot\n"
 -            + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\t0.0\n"
              + "Turn\tFER_CAPAA\t-1\t36\t38\tGAMMA-TURN\t0.0\n"
 +            + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\t0.0\n"
              + "<html>Pfam domain<a href=\"http://pfam.xfam.org/family/PF00111\">Pfam_3_4</a></html>\tFER_CAPAA\t-1\t20\t20\tPfam\t0.0\n"
 -            + "ENDGROUP\tuniprot\n";
 +            + "ENDGROUP\tuniprot\n"
 +            // null / empty group features output after features in named
 +            // groups:
 +            + "desc2\tFER_CAPAN\t-1\t4\t9\tPfam\n"
 +            + "desc4\tFER1_SOLLC\t-1\t5\t8\tPfam\t-2.6\n";
 +    assertEquals(expected, exported);
 +  }
 +
 +  @Test(groups = { "Functional" })
 +  public void testPrintGffFormat() throws Exception
 +  {
 +    File f = new File("examples/uniref50.fa");
 +    AlignmentI al = readAlignmentFile(f);
 +    AlignFrame af = new AlignFrame(al, 500, 500);
 +
 +    /*
 +     * no features
 +     */
 +    FeaturesFile featuresFile = new FeaturesFile();
 +    FeatureRenderer fr = af.alignPanel.getFeatureRenderer();
 +    Map<String, FeatureColourI> visible = new HashMap<String, FeatureColourI>();
 +    List<String> visibleGroups = new ArrayList<String>(
 +            Arrays.asList(new String[] {}));
 +    String exported = featuresFile.printGffFormat(al.getSequencesArray(),
 +            visible, visibleGroups, false);
 +    String gffHeader = "##gff-version 2\n";
 +    assertEquals(gffHeader, exported);
 +    exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
 +            visibleGroups, true);
 +    assertEquals(gffHeader, exported);
 +
 +    /*
 +     * add some features
 +     */
 +    al.getSequenceAt(0).addSequenceFeature(
 +            new SequenceFeature("Domain", "Cath", 0, 0, 0f, "Uniprot"));
 +    al.getSequenceAt(0).addSequenceFeature(
 +            new SequenceFeature("METAL", "Cath", 39, 39, 1.2f, null));
 +    al.getSequenceAt(1)
 +            .addSequenceFeature(
 +                    new SequenceFeature("GAMMA-TURN", "Turn", 36, 38, 2.1f,
 +                            "s3dm"));
 +    SequenceFeature sf = new SequenceFeature("Pfam", "", 20, 20, 0f,
 +            "Uniprot");
 +    sf.setAttributes("x=y;black=white");
 +    sf.setStrand("+");
 +    sf.setPhase("2");
 +    al.getSequenceAt(1).addSequenceFeature(sf);
 +
 +    /*
 +     * with no features displayed, exclude non-positional features
 +     */
 +    exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
 +            visibleGroups, false);
 +    assertEquals(gffHeader, exported);
 +
 +    /*
 +     * include non-positional features
 +     */
 +    visibleGroups.add("Uniprot");
 +    exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
 +            visibleGroups, true);
 +    String expected = gffHeader
 +            + "FER_CAPAA\tUniprot\tDomain\t0\t0\t0.0\t.\t.\n";
 +    assertEquals(expected, exported);
 +
 +    /*
 +     * set METAL (in uniprot group) and GAMMA-TURN visible, but not Pfam
 +     * only Uniprot group visible here...
 +     */
 +    fr.setVisible("METAL");
 +    fr.setVisible("GAMMA-TURN");
 +    visible = fr.getDisplayedFeatureCols();
 +    exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
 +            visibleGroups, false);
 +    // METAL feature has null group: description used for column 2
 +    expected = gffHeader + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n";
 +    assertEquals(expected, exported);
 +
 +    /*
 +     * set s3dm group visible
 +     */
 +    visibleGroups.add("s3dm");
 +    exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
 +            visibleGroups, false);
 +    // METAL feature has null group: description used for column 2
 +    expected = gffHeader + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n"
 +            + "FER_CAPAN\ts3dm\tGAMMA-TURN\t36\t38\t2.1\t.\t.\n";
 +    assertEquals(expected, exported);
 +
 +    /*
 +     * now set Pfam visible
 +     */
 +    fr.setVisible("Pfam");
 +    visible = fr.getDisplayedFeatureCols();
 +    exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
 +            visibleGroups, false);
 +    // Pfam feature columns include strand(+), phase(2), attributes
 +    expected = gffHeader
 +            + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n"
 +            + "FER_CAPAN\ts3dm\tGAMMA-TURN\t36\t38\t2.1\t.\t.\n"
 +            + "FER_CAPAN\tUniprot\tPfam\t20\t20\t0.0\t+\t2\tx=y;black=white\n";
      assertEquals(expected, exported);
    }
  }