JAL-2446 merged to spike branch
[jalview.git] / test / jalview / io / FeaturesFileTest.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.io;
22
23 import static org.testng.AssertJUnit.assertEquals;
24 import static org.testng.AssertJUnit.assertFalse;
25 import static org.testng.AssertJUnit.assertNotNull;
26 import static org.testng.AssertJUnit.assertTrue;
27
28 import jalview.api.FeatureColourI;
29 import jalview.api.FeatureRenderer;
30 import jalview.datamodel.Alignment;
31 import jalview.datamodel.AlignmentI;
32 import jalview.datamodel.SequenceDummy;
33 import jalview.datamodel.SequenceFeature;
34 import jalview.datamodel.SequenceI;
35 import jalview.datamodel.features.SequenceFeatures;
36 import jalview.gui.AlignFrame;
37 import jalview.gui.JvOptionPane;
38
39 import java.awt.Color;
40 import java.io.File;
41 import java.io.IOException;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.HashMap;
45 import java.util.List;
46 import java.util.Map;
47
48 import org.testng.annotations.BeforeClass;
49 import org.testng.annotations.Test;
50
51 public class FeaturesFileTest
52 {
53
54   @BeforeClass(alwaysRun = true)
55   public void setUpJvOptionPane()
56   {
57     JvOptionPane.setInteractiveMode(false);
58     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
59   }
60
61   private static String simpleGffFile = "examples/testdata/simpleGff3.gff";
62
63   @Test(groups = { "Functional" })
64   public void testParse() throws Exception
65   {
66     File f = new File("examples/uniref50.fa");
67     AlignmentI al = readAlignmentFile(f);
68     AlignFrame af = new AlignFrame(al, 500, 500);
69     Map<String, FeatureColourI> colours = af.getFeatureRenderer()
70             .getFeatureColours();
71     FeaturesFile featuresFile = new FeaturesFile(
72             "examples/exampleFeatures.txt", DataSourceType.FILE);
73     assertTrue("Test " + "Features file test"
74             + "\nFailed to parse features file.",
75             featuresFile.parse(al.getDataset(), colours, true));
76
77     /*
78      * Refetch the colour map from the FeatureRenderer (to confirm it has been
79      * updated - JAL-1904), and verify (some) feature group colours
80      */
81     colours = af.getFeatureRenderer().getFeatureColours();
82     assertEquals("26 feature group colours not found", 26, colours.size());
83     assertEquals(colours.get("Cath").getColour(), new Color(0x93b1d1));
84     assertEquals(colours.get("ASX-MOTIF").getColour(), new Color(0x6addbb));
85
86     /*
87      * verify (some) features on sequences
88      */
89     List<SequenceFeature> sfs = al.getSequenceAt(0).getDatasetSequence()
90             .getSequenceFeatures(); // FER_CAPAA
91     SequenceFeatures.sortFeatures(sfs, true);
92     assertEquals(8, sfs.size());
93
94     /*
95      * verify (in ascending start position order)
96      */
97     SequenceFeature sf = sfs.get(0);
98     assertEquals("Pfam family%LINK%", sf.description);
99     assertEquals(0, sf.begin);
100     assertEquals(0, sf.end);
101     assertEquals("uniprot", sf.featureGroup);
102     assertEquals("Pfam", sf.type);
103     assertEquals(1, sf.links.size());
104     assertEquals("Pfam family|http://pfam.xfam.org/family/PF00111",
105             sf.links.get(0));
106
107     sf = sfs.get(1);
108     assertEquals("Ferredoxin_fold Status: True Positive ", sf.description);
109     assertEquals(3, sf.begin);
110     assertEquals(93, sf.end);
111     assertEquals("uniprot", sf.featureGroup);
112     assertEquals("Cath", sf.type);
113
114     sf = sfs.get(2);
115     assertEquals("Fer2 Status: True Positive Pfam 8_8%LINK%",
116             sf.description);
117     assertEquals("Pfam 8_8|http://pfam.xfam.org/family/PF00111",
118             sf.links.get(0));
119     assertEquals(8, sf.begin);
120     assertEquals(83, sf.end);
121     assertEquals("uniprot", sf.featureGroup);
122     assertEquals("Pfam", sf.type);
123
124     sf = sfs.get(3);
125     assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
126     assertEquals(39, sf.begin);
127     assertEquals(39, sf.end);
128     assertEquals("uniprot", sf.featureGroup);
129     assertEquals("METAL", sf.type);
130
131     sf = sfs.get(4);
132     assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
133     assertEquals(44, sf.begin);
134     assertEquals(44, sf.end);
135     assertEquals("uniprot", sf.featureGroup);
136     assertEquals("METAL", sf.type);
137
138     sf = sfs.get(5);
139     assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
140     assertEquals(47, sf.begin);
141     assertEquals(47, sf.end);
142     assertEquals("uniprot", sf.featureGroup);
143     assertEquals("METAL", sf.type);
144
145     sf = sfs.get(6);
146     assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
147     assertEquals(77, sf.begin);
148     assertEquals(77, sf.end);
149     assertEquals("uniprot", sf.featureGroup);
150     assertEquals("METAL", sf.type);
151
152     sf = sfs.get(7);
153     assertEquals(
154             "High confidence server. Only hits with scores over 0.8 are reported. PHOSPHORYLATION (T) 89_8%LINK%",
155             sf.description);
156     assertEquals(
157             "PHOSPHORYLATION (T) 89_8|http://www.cbs.dtu.dk/cgi-bin/proview/webface-link?seqid=P83527&amp;service=NetPhos-2.0",
158             sf.links.get(0));
159     assertEquals(89, sf.begin);
160     assertEquals(89, sf.end);
161     assertEquals("netphos", sf.featureGroup);
162     assertEquals("PHOSPHORYLATION (T)", sf.type);
163   }
164
165   /**
166    * Test parsing a features file with a mix of Jalview and GFF formatted
167    * content
168    * 
169    * @throws Exception
170    */
171   @Test(groups = { "Functional" })
172   public void testParse_mixedJalviewGff() throws Exception
173   {
174     File f = new File("examples/uniref50.fa");
175     AlignmentI al = readAlignmentFile(f);
176     AlignFrame af = new AlignFrame(al, 500, 500);
177     Map<String, FeatureColourI> colours = af.getFeatureRenderer()
178             .getFeatureColours();
179     // GFF2 uses space as name/value separator in column 9
180     String gffData = "METAL\tcc9900\n"
181             + "GFF\n"
182             + "FER_CAPAA\tuniprot\tMETAL\t44\t45\t4.0\t.\t.\tNote Iron-sulfur; Note 2Fe-2S\n"
183             + "FER1_SOLLC\tuniprot\tPfam\t55\t130\t2.0\t.\t.";
184     FeaturesFile featuresFile = new FeaturesFile(gffData,
185             DataSourceType.PASTE);
186     assertTrue("Failed to parse features file",
187             featuresFile.parse(al.getDataset(), colours, true));
188
189     // verify colours read or synthesized
190     colours = af.getFeatureRenderer().getFeatureColours();
191     assertEquals("1 feature group colours not found", 1, colours.size());
192     assertEquals(colours.get("METAL").getColour(), new Color(0xcc9900));
193
194     // verify feature on FER_CAPAA
195     List<SequenceFeature> sfs = al.getSequenceAt(0).getDatasetSequence()
196             .getSequenceFeatures();
197     assertEquals(1, sfs.size());
198     SequenceFeature sf = sfs.get(0);
199     assertEquals("Iron-sulfur,2Fe-2S", sf.description);
200     assertEquals(44, sf.begin);
201     assertEquals(45, sf.end);
202     assertEquals("uniprot", sf.featureGroup);
203     assertEquals("METAL", sf.type);
204     assertEquals(4f, sf.getScore(), 0.001f);
205
206     // verify feature on FER1_SOLLC
207     sfs = al.getSequenceAt(2).getDatasetSequence().getSequenceFeatures();
208     assertEquals(1, sfs.size());
209     sf = sfs.get(0);
210     assertEquals("uniprot", sf.description);
211     assertEquals(55, sf.begin);
212     assertEquals(130, sf.end);
213     assertEquals("uniprot", sf.featureGroup);
214     assertEquals("Pfam", sf.type);
215     assertEquals(2f, sf.getScore(), 0.001f);
216   }
217
218   public static AlignmentI readAlignmentFile(File f) throws IOException
219   {
220     System.out.println("Reading file: " + f);
221     String ff = f.getPath();
222     FormatAdapter rf = new FormatAdapter();
223
224     AlignmentI al = rf.readFile(ff, DataSourceType.FILE,
225             new IdentifyFile().identify(ff, DataSourceType.FILE));
226
227     al.setDataset(null); // creates dataset sequences
228     assertNotNull("Couldn't read supplied alignment data.", al);
229     return al;
230   }
231
232   /**
233    * Test parsing a features file with GFF formatted content only
234    * 
235    * @throws Exception
236    */
237   @Test(groups = { "Functional" })
238   public void testParse_pureGff3() throws Exception
239   {
240     File f = new File("examples/uniref50.fa");
241     AlignmentI al = readAlignmentFile(f);
242     AlignFrame af = new AlignFrame(al, 500, 500);
243     Map<String, FeatureColourI> colours = af.getFeatureRenderer()
244             .getFeatureColours();
245     // GFF3 uses '=' separator for name/value pairs in colum 9
246     String gffData = "##gff-version 3\n"
247             + "FER_CAPAA\tuniprot\tMETAL\t39\t39\t0.0\t.\t.\t"
248             + "Note=Iron-sulfur (2Fe-2S);Note=another note;evidence=ECO:0000255|PROSITE-ProRule:PRU00465\n"
249             + "FER1_SOLLC\tuniprot\tPfam\t55\t130\t3.0\t.\t.\tID=$23";
250     FeaturesFile featuresFile = new FeaturesFile(gffData,
251             DataSourceType.PASTE);
252     assertTrue("Failed to parse features file",
253             featuresFile.parse(al.getDataset(), colours, true));
254
255     // verify feature on FER_CAPAA
256     List<SequenceFeature> sfs = al.getSequenceAt(0).getDatasetSequence()
257             .getSequenceFeatures();
258     assertEquals(1, sfs.size());
259     SequenceFeature sf = sfs.get(0);
260     // description parsed from Note attribute
261     assertEquals("Iron-sulfur (2Fe-2S),another note", sf.description);
262     assertEquals(39, sf.begin);
263     assertEquals(39, sf.end);
264     assertEquals("uniprot", sf.featureGroup);
265     assertEquals("METAL", sf.type);
266     assertEquals(
267             "Note=Iron-sulfur (2Fe-2S);Note=another note;evidence=ECO:0000255|PROSITE-ProRule:PRU00465",
268             sf.getValue("ATTRIBUTES"));
269
270     // verify feature on FER1_SOLLC1
271     sfs = al.getSequenceAt(2).getDatasetSequence().getSequenceFeatures();
272     assertEquals(1, sfs.size());
273     sf = sfs.get(0);
274     // ID used for description if available
275     assertEquals("$23", sf.description);
276     assertEquals(55, sf.begin);
277     assertEquals(130, sf.end);
278     assertEquals("uniprot", sf.featureGroup);
279     assertEquals("Pfam", sf.type);
280     assertEquals(3f, sf.getScore(), 0.001f);
281   }
282
283   /**
284    * Test parsing a features file with Jalview format features (but no colour
285    * descriptors or startgroup to give the hint not to parse as GFF)
286    * 
287    * @throws Exception
288    */
289   @Test(groups = { "Functional" })
290   public void testParse_jalviewFeaturesOnly() throws Exception
291   {
292     File f = new File("examples/uniref50.fa");
293     AlignmentI al = readAlignmentFile(f);
294     AlignFrame af = new AlignFrame(al, 500, 500);
295     Map<String, FeatureColourI> colours = af.getFeatureRenderer()
296             .getFeatureColours();
297
298     /*
299      * one feature on FER_CAPAA and one on sequence 3 (index 2) FER1_SOLLC
300      */
301     String featureData = "Iron-sulfur (2Fe-2S)\tFER_CAPAA\t-1\t39\t39\tMETAL\n"
302             + "Iron-phosphorus (2Fe-P)\tID_NOT_SPECIFIED\t2\t86\t87\tMETALLIC\n";
303     FeaturesFile featuresFile = new FeaturesFile(featureData,
304             DataSourceType.PASTE);
305     assertTrue("Failed to parse features file",
306             featuresFile.parse(al.getDataset(), colours, true));
307
308     // verify FER_CAPAA feature
309     List<SequenceFeature> sfs = al.getSequenceAt(0).getDatasetSequence()
310             .getSequenceFeatures();
311     assertEquals(1, sfs.size());
312     SequenceFeature sf = sfs.get(0);
313     assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
314     assertEquals(39, sf.begin);
315     assertEquals(39, sf.end);
316     assertEquals("METAL", sf.type);
317
318     // verify FER1_SOLLC feature
319     sfs = al.getSequenceAt(2).getDatasetSequence().getSequenceFeatures();
320     assertEquals(1, sfs.size());
321     sf = sfs.get(0);
322     assertEquals("Iron-phosphorus (2Fe-P)", sf.description);
323     assertEquals(86, sf.begin);
324     assertEquals(87, sf.end);
325     assertEquals("METALLIC", sf.type);
326   }
327
328   private void checkDatasetfromSimpleGff3(AlignmentI dataset)
329   {
330     assertEquals("no sequences extracted from GFF3 file", 2,
331             dataset.getHeight());
332
333     SequenceI seq1 = dataset.findName("seq1");
334     SequenceI seq2 = dataset.findName("seq2");
335     assertNotNull(seq1);
336     assertNotNull(seq2);
337     assertFalse(
338             "Failed to replace dummy seq1 with real sequence",
339             seq1 instanceof SequenceDummy
340                     && ((SequenceDummy) seq1).isDummy());
341     assertFalse(
342             "Failed to replace dummy seq2 with real sequence",
343             seq2 instanceof SequenceDummy
344                     && ((SequenceDummy) seq2).isDummy());
345     String placeholderseq = new SequenceDummy("foo").getSequenceAsString();
346     assertFalse("dummy replacement buggy for seq1",
347             placeholderseq.equals(seq1.getSequenceAsString()));
348     assertFalse("dummy replacement buggy for seq2",
349             placeholderseq.equals(seq2.getSequenceAsString()));
350     assertNotNull("No features added to seq1", seq1.getSequenceFeatures());
351     assertEquals("Wrong number of features", 3, seq1.getSequenceFeatures()
352             .size());
353     assertTrue(seq2.getSequenceFeatures().isEmpty());
354     assertEquals(
355             "Wrong number of features",
356             0,
357             seq2.getSequenceFeatures() == null ? 0 : seq2
358                     .getSequenceFeatures().size());
359     assertTrue(
360             "Expected at least one CDNA/Protein mapping for seq1",
361             dataset.getCodonFrame(seq1) != null
362                     && dataset.getCodonFrame(seq1).size() > 0);
363
364   }
365
366   @Test(groups = { "Functional" })
367   public void readGff3File() throws IOException
368   {
369     FeaturesFile gffreader = new FeaturesFile(true, simpleGffFile,
370             DataSourceType.FILE);
371     Alignment dataset = new Alignment(gffreader.getSeqsAsArray());
372     gffreader.addProperties(dataset);
373     checkDatasetfromSimpleGff3(dataset);
374   }
375
376   @Test(groups = { "Functional" })
377   public void simpleGff3FileClass() throws IOException
378   {
379     AlignmentI dataset = new Alignment(new SequenceI[] {});
380     FeaturesFile ffile = new FeaturesFile(simpleGffFile,
381             DataSourceType.FILE);
382   
383     boolean parseResult = ffile.parse(dataset, null, false, false);
384     assertTrue("return result should be true", parseResult);
385     checkDatasetfromSimpleGff3(dataset);
386   }
387
388   @Test(groups = { "Functional" })
389   public void simpleGff3FileLoader() throws IOException
390   {
391     AlignFrame af = new FileLoader(false).LoadFileWaitTillLoaded(
392             simpleGffFile, DataSourceType.FILE);
393     assertTrue(
394             "Didn't read the alignment into an alignframe from Gff3 File",
395             af != null);
396     checkDatasetfromSimpleGff3(af.getViewport().getAlignment());
397   }
398
399   @Test(groups = { "Functional" })
400   public void simpleGff3RelaxedIdMatching() throws IOException
401   {
402     AlignmentI dataset = new Alignment(new SequenceI[] {});
403     FeaturesFile ffile = new FeaturesFile(simpleGffFile,
404             DataSourceType.FILE);
405   
406     boolean parseResult = ffile.parse(dataset, null, false, true);
407     assertTrue("return result (relaxedID matching) should be true",
408             parseResult);
409     checkDatasetfromSimpleGff3(dataset);
410   }
411
412   @Test(groups = { "Functional" })
413   public void testPrintJalviewFormat() throws Exception
414   {
415     File f = new File("examples/uniref50.fa");
416     AlignmentI al = readAlignmentFile(f);
417     AlignFrame af = new AlignFrame(al, 500, 500);
418     Map<String, FeatureColourI> colours = af.getFeatureRenderer()
419             .getFeatureColours();
420     String features = "METAL\tcc9900\n"
421             + "GAMMA-TURN\tred|0,255,255|20.0|95.0|below|66.0\n"
422             + "Pfam\tred\n"
423             + "STARTGROUP\tuniprot\n"
424             + "Cath\tFER_CAPAA\t-1\t0\t0\tDomain\n" // non-positional feature
425             + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\n"
426             + "Turn\tFER_CAPAA\t-1\t36\t38\tGAMMA-TURN\n"
427             + "<html>Pfam domain<a href=\"http://pfam.xfam.org/family/PF00111\">Pfam_3_4</a></html>\tFER_CAPAA\t-1\t20\t20\tPfam\n"
428             + "ENDGROUP\tuniprot\n";
429     FeaturesFile featuresFile = new FeaturesFile(features,
430             DataSourceType.PASTE);
431     featuresFile.parse(al.getDataset(), colours, false);
432
433     /*
434      * add positional and non-positional features with null and
435      * empty feature group to check handled correctly
436      */
437     SequenceI seq = al.getSequenceAt(1); // FER_CAPAN
438     seq.addSequenceFeature(new SequenceFeature("Pfam", "desc1", 0, 0, 1.3f,
439             null));
440     seq.addSequenceFeature(new SequenceFeature("Pfam", "desc2", 4, 9,
441             Float.NaN, null));
442     seq = al.getSequenceAt(2); // FER1_SOLLC
443     seq.addSequenceFeature(new SequenceFeature("Pfam", "desc3", 0, 0,
444             Float.NaN, ""));
445     seq.addSequenceFeature(new SequenceFeature("Pfam", "desc4", 5, 8,
446             -2.6f, ""));
447
448     /*
449      * first with no features displayed, exclude non-positional features
450      */
451     FeatureRenderer fr = af.alignPanel.getFeatureRenderer();
452     Map<String, FeatureColourI> visible = fr.getDisplayedFeatureCols();
453     List<String> visibleGroups = new ArrayList<String>(
454             Arrays.asList(new String[] {}));
455     String exported = featuresFile.printJalviewFormat(
456             al.getSequencesArray(), visible, visibleGroups, false);
457     String expected = "No Features Visible";
458     assertEquals(expected, exported);
459
460     /*
461      * include non-positional features
462      */
463     visibleGroups.add("uniprot");
464     exported = featuresFile.printJalviewFormat(al.getSequencesArray(),
465             visible, visibleGroups, true);
466     expected = "Cath\tFER_CAPAA\t-1\t0\t0\tDomain\t0.0\n"
467             + "desc1\tFER_CAPAN\t-1\t0\t0\tPfam\t1.3\n"
468             + "desc3\tFER1_SOLLC\t-1\t0\t0\tPfam\n" // NaN is not output
469             + "\nSTARTGROUP\tuniprot\nENDGROUP\tuniprot\n";
470     assertEquals(expected, exported);
471
472     /*
473      * set METAL (in uniprot group) and GAMMA-TURN visible, but not Pfam
474      */
475     fr.setVisible("METAL");
476     fr.setVisible("GAMMA-TURN");
477     visible = fr.getDisplayedFeatureCols();
478     exported = featuresFile.printJalviewFormat(al.getSequencesArray(),
479             visible, visibleGroups, false);
480     expected = "METAL\tcc9900\n"
481             + "GAMMA-TURN\tff0000|00ffff|20.0|95.0|below|66.0\n"
482             + "\nSTARTGROUP\tuniprot\n"
483             + "Turn\tFER_CAPAA\t-1\t36\t38\tGAMMA-TURN\t0.0\n"
484             + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\t0.0\n"
485             + "ENDGROUP\tuniprot\n";
486     assertEquals(expected, exported);
487
488     /*
489      * now set Pfam visible
490      */
491     fr.setVisible("Pfam");
492     visible = fr.getDisplayedFeatureCols();
493     exported = featuresFile.printJalviewFormat(al.getSequencesArray(),
494             visible, visibleGroups, false);
495     /*
496      * features are output within group, ordered by sequence and by type
497      */
498     expected = "METAL\tcc9900\n"
499             + "Pfam\tff0000\n"
500             + "GAMMA-TURN\tff0000|00ffff|20.0|95.0|below|66.0\n"
501             + "\nSTARTGROUP\tuniprot\n"
502             + "Turn\tFER_CAPAA\t-1\t36\t38\tGAMMA-TURN\t0.0\n"
503             + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\t0.0\n"
504             + "<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"
505             + "ENDGROUP\tuniprot\n"
506             // null / empty group features output after features in named
507             // groups:
508             + "desc2\tFER_CAPAN\t-1\t4\t9\tPfam\n"
509             + "desc4\tFER1_SOLLC\t-1\t5\t8\tPfam\t-2.6\n";
510     assertEquals(expected, exported);
511   }
512
513   @Test(groups = { "Functional" })
514   public void testPrintGffFormat() throws Exception
515   {
516     File f = new File("examples/uniref50.fa");
517     AlignmentI al = readAlignmentFile(f);
518     AlignFrame af = new AlignFrame(al, 500, 500);
519
520     /*
521      * no features
522      */
523     FeaturesFile featuresFile = new FeaturesFile();
524     FeatureRenderer fr = af.alignPanel.getFeatureRenderer();
525     Map<String, FeatureColourI> visible = new HashMap<String, FeatureColourI>();
526     List<String> visibleGroups = new ArrayList<String>(
527             Arrays.asList(new String[] {}));
528     String exported = featuresFile.printGffFormat(al.getSequencesArray(),
529             visible, visibleGroups, false);
530     String gffHeader = "##gff-version 2\n";
531     assertEquals(gffHeader, exported);
532     exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
533             visibleGroups, true);
534     assertEquals(gffHeader, exported);
535
536     /*
537      * add some features
538      */
539     al.getSequenceAt(0).addSequenceFeature(
540             new SequenceFeature("Domain", "Cath", 0, 0, 0f, "Uniprot"));
541     al.getSequenceAt(0).addSequenceFeature(
542             new SequenceFeature("METAL", "Cath", 39, 39, 1.2f, null));
543     al.getSequenceAt(1)
544             .addSequenceFeature(
545                     new SequenceFeature("GAMMA-TURN", "Turn", 36, 38, 2.1f,
546                             "s3dm"));
547     SequenceFeature sf = new SequenceFeature("Pfam", "", 20, 20, 0f,
548             "Uniprot");
549     sf.setAttributes("x=y;black=white");
550     sf.setStrand("+");
551     sf.setPhase("2");
552     al.getSequenceAt(1).addSequenceFeature(sf);
553
554     /*
555      * with no features displayed, exclude non-positional features
556      */
557     exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
558             visibleGroups, false);
559     assertEquals(gffHeader, exported);
560
561     /*
562      * include non-positional features
563      */
564     visibleGroups.add("Uniprot");
565     exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
566             visibleGroups, true);
567     String expected = gffHeader
568             + "FER_CAPAA\tUniprot\tDomain\t0\t0\t0.0\t.\t.\n";
569     assertEquals(expected, exported);
570
571     /*
572      * set METAL (in uniprot group) and GAMMA-TURN visible, but not Pfam
573      * only Uniprot group visible here...
574      */
575     fr.setVisible("METAL");
576     fr.setVisible("GAMMA-TURN");
577     visible = fr.getDisplayedFeatureCols();
578     exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
579             visibleGroups, false);
580     // METAL feature has null group: description used for column 2
581     expected = gffHeader + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n";
582     assertEquals(expected, exported);
583
584     /*
585      * set s3dm group visible
586      */
587     visibleGroups.add("s3dm");
588     exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
589             visibleGroups, false);
590     // METAL feature has null group: description used for column 2
591     expected = gffHeader + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n"
592             + "FER_CAPAN\ts3dm\tGAMMA-TURN\t36\t38\t2.1\t.\t.\n";
593     assertEquals(expected, exported);
594
595     /*
596      * now set Pfam visible
597      */
598     fr.setVisible("Pfam");
599     visible = fr.getDisplayedFeatureCols();
600     exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
601             visibleGroups, false);
602     // Pfam feature columns include strand(+), phase(2), attributes
603     expected = gffHeader
604             + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n"
605             + "FER_CAPAN\ts3dm\tGAMMA-TURN\t36\t38\t2.1\t.\t.\n"
606             + "FER_CAPAN\tUniprot\tPfam\t20\t20\t0.0\t+\t2\tx=y;black=white\n";
607     assertEquals(expected, exported);
608   }
609 }