JAL-3567 report mapped location for virtual features in tooltip etc
[jalview.git] / test / jalview / io / SequenceAnnotationReportTest.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.assertTrue;
25
26 import java.awt.Color;
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.Map;
30
31 import org.testng.annotations.BeforeClass;
32 import org.testng.annotations.Test;
33
34 import jalview.api.FeatureColourI;
35 import jalview.datamodel.DBRefEntry;
36 import jalview.datamodel.Sequence;
37 import jalview.datamodel.SequenceFeature;
38 import jalview.datamodel.SequenceI;
39 import jalview.gui.JvOptionPane;
40 import jalview.io.gff.GffConstants;
41 import jalview.renderer.seqfeatures.FeatureRenderer;
42 import jalview.schemes.FeatureColour;
43 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
44 import junit.extensions.PA;
45
46 public class SequenceAnnotationReportTest
47 {
48
49   @BeforeClass(alwaysRun = true)
50   public void setUpJvOptionPane()
51   {
52     JvOptionPane.setInteractiveMode(false);
53     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
54   }
55
56   @Test(groups = "Functional")
57   public void testAppendFeature_disulfideBond()
58   {
59     SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
60     StringBuilder sb = new StringBuilder();
61     sb.append("123456");
62     SequenceFeature sf = new SequenceFeature("disulfide bond", "desc", 1,
63             3, 1.2f, "group");
64
65     // residuePos == 2 does not match start or end of feature, nothing done:
66     sar.appendFeature(sb, 2, null, sf, null, 0);
67     assertEquals("123456", sb.toString());
68
69     // residuePos == 1 matches start of feature, text appended (but no <br/>)
70     // feature score is not included
71     sar.appendFeature(sb, 1, null, sf, null, 0);
72     assertEquals("123456disulfide bond 1:3", sb.toString());
73
74     // residuePos == 3 matches end of feature, text appended
75     // <br/> is prefixed once sb.length() > 6
76     sar.appendFeature(sb, 3, null, sf, null, 0);
77     assertEquals("123456disulfide bond 1:3<br/>disulfide bond 1:3",
78             sb.toString());
79   }
80
81   @Test(groups = "Functional")
82   public void testAppendFeatures_longText()
83   {
84     SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
85     StringBuilder sb = new StringBuilder();
86     String longString = "Abcd".repeat(50);
87     SequenceFeature sf = new SequenceFeature("sequence", longString, 1, 3,
88             "group");
89
90     sar.appendFeature(sb, 1, null, sf, null, 0);
91     assertTrue(sb.length() < 100);
92
93     List<SequenceFeature> sfl = new ArrayList<>();
94     sb.setLength(0);
95     sfl.add(sf);
96     sfl.add(sf);
97     sfl.add(sf);
98     sfl.add(sf);
99     sfl.add(sf);
100     sfl.add(sf);
101     sfl.add(sf);
102     sfl.add(sf);
103     sfl.add(sf);
104     sfl.add(sf);
105     int n = sar.appendFeatures(sb, 1, sfl,
106             new FeatureRenderer(null), 200); // text should terminate before 200 characters
107     String s = sb.toString();
108     assertTrue(s.length() < 200);
109     assertEquals(n, 7); // should be 7 features left over
110
111   }
112
113   @Test(groups = "Functional")
114   public void testAppendFeature_status()
115   {
116     SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
117     StringBuilder sb = new StringBuilder();
118     SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3,
119             Float.NaN, "group");
120     sf.setStatus("Confirmed");
121
122     sar.appendFeature(sb, 1, null, sf, null, 0);
123     assertEquals("METAL 1 3; Fe2-S; (Confirmed)", sb.toString());
124   }
125
126   @Test(groups = "Functional")
127   public void testAppendFeature_withScore()
128   {
129     SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
130     StringBuilder sb = new StringBuilder();
131     SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3, 1.3f,
132             "group");
133
134     FeatureRendererModel fr = new FeatureRenderer(null);
135     Map<String, float[][]> minmax = fr.getMinMax();
136     sar.appendFeature(sb, 1, fr, sf, null, 0);
137     /*
138      * map has no entry for this feature type - score is not shown:
139      */
140     assertEquals("METAL 1 3; Fe2-S", sb.toString());
141
142     /*
143      * map has entry for this feature type - score is shown:
144      */
145     minmax.put("METAL", new float[][] { { 0f, 1f }, null });
146     sar.appendFeature(sb, 1, fr, sf, null, 0);
147     // <br/> is appended to a buffer > 6 in length
148     assertEquals("METAL 1 3; Fe2-S<br/>METAL 1 3; Fe2-S Score=1.3",
149             sb.toString());
150
151     /*
152      * map has min == max for this feature type - score is not shown:
153      */
154     minmax.put("METAL", new float[][] { { 2f, 2f }, null });
155     sb.setLength(0);
156     sar.appendFeature(sb, 1, fr, sf, null, 0);
157     assertEquals("METAL 1 3; Fe2-S", sb.toString());
158   }
159
160   @Test(groups = "Functional")
161   public void testAppendFeature_noScore()
162   {
163     SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
164     StringBuilder sb = new StringBuilder();
165     SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3,
166             Float.NaN, "group");
167
168     sar.appendFeature(sb, 1, null, sf, null, 0);
169     assertEquals("METAL 1 3; Fe2-S", sb.toString());
170   }
171
172   /**
173    * A specific attribute value is included if it is used to colour the feature
174    */
175   @Test(groups = "Functional")
176   public void testAppendFeature_colouredByAttribute()
177   {
178     SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
179     StringBuilder sb = new StringBuilder();
180     SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3,
181             Float.NaN, "group");
182     sf.setValue("clinical_significance", "Benign");
183
184     /*
185      * first with no colour by attribute
186      */
187     FeatureRendererModel fr = new FeatureRenderer(null);
188     sar.appendFeature(sb, 1, fr, sf, null, 0);
189     assertEquals("METAL 1 3; Fe2-S", sb.toString());
190
191     /*
192      * then with colour by an attribute the feature lacks
193      */
194     FeatureColourI fc = new FeatureColour(null, Color.white, Color.black,
195             null, 5, 10);
196     fc.setAttributeName("Pfam");
197     fr.setColour("METAL", fc);
198     sb.setLength(0);
199     sar.appendFeature(sb, 1, fr, sf, null, 0);
200     assertEquals("METAL 1 3; Fe2-S", sb.toString()); // no change
201
202     /*
203      * then with colour by an attribute the feature has
204      */
205     fc.setAttributeName("clinical_significance");
206     sb.setLength(0);
207     sar.appendFeature(sb, 1, fr, sf, null, 0);
208     assertEquals("METAL 1 3; Fe2-S; clinical_significance=Benign",
209             sb.toString());
210   }
211
212   @Test(groups = "Functional")
213   public void testAppendFeature_withScoreStatusAttribute()
214   {
215     SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
216     StringBuilder sb = new StringBuilder();
217     SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3, 1.3f,
218             "group");
219     sf.setStatus("Confirmed");
220     sf.setValue("clinical_significance", "Benign");
221
222     FeatureRendererModel fr = new FeatureRenderer(null);
223     Map<String, float[][]> minmax = fr.getMinMax();
224     FeatureColourI fc = new FeatureColour(null, Color.white, Color.blue,
225             null, 12, 22);
226     fc.setAttributeName("clinical_significance");
227     fr.setColour("METAL", fc);
228     minmax.put("METAL", new float[][] { { 0f, 1f }, null });
229     sar.appendFeature(sb, 1, fr, sf, null, 0);
230
231     assertEquals(
232             "METAL 1 3; Fe2-S Score=1.3; (Confirmed); clinical_significance=Benign",
233             sb.toString());
234   }
235
236   @Test(groups = "Functional")
237   public void testAppendFeature_DescEqualsType()
238   {
239     SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
240     StringBuilder sb = new StringBuilder();
241     SequenceFeature sf = new SequenceFeature("METAL", "METAL", 1, 3,
242             Float.NaN, "group");
243
244     // description is not included if it duplicates type:
245     sar.appendFeature(sb, 1, null, sf, null, 0);
246     assertEquals("METAL 1 3", sb.toString());
247
248     sb.setLength(0);
249     sf.setDescription("Metal");
250     // test is case-sensitive:
251     sar.appendFeature(sb, 1, null, sf, null, 0);
252     assertEquals("METAL 1 3; Metal", sb.toString());
253   }
254
255   @Test(groups = "Functional")
256   public void testAppendFeature_stripHtml()
257   {
258     SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
259     StringBuilder sb = new StringBuilder();
260     SequenceFeature sf = new SequenceFeature("METAL",
261             "<html><body>hello<em>world</em></body></html>", 1, 3,
262             Float.NaN, "group");
263
264     sar.appendFeature(sb, 1, null, sf, null, 0);
265     // !! strips off </body> but not <body> ??
266     assertEquals("METAL 1 3; <body>hello<em>world</em>", sb.toString());
267
268     sb.setLength(0);
269     sf.setDescription("<br>&kHD>6");
270     sar.appendFeature(sb, 1, null, sf, null, 0);
271     // if no <html> tag, html-encodes > and < (only):
272     assertEquals("METAL 1 3; &lt;br&gt;&kHD&gt;6", sb.toString());
273   }
274
275   @Test(groups = "Functional")
276   public void testCreateSequenceAnnotationReport()
277   {
278     SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
279     StringBuilder sb = new StringBuilder();
280
281     SequenceI seq = new Sequence("s1", "MAKLKRFQSSTLL");
282     seq.setDescription("SeqDesc");
283
284     /*
285      * positional features are ignored
286      */
287     seq.addSequenceFeature(new SequenceFeature("Domain", "Ferredoxin", 5,
288             10, 1f, null));
289     sar.createSequenceAnnotationReport(sb, seq, true, true, null);
290     assertEquals("<i>SeqDesc</i>", sb.toString());
291
292     /*
293      * non-positional feature
294      */
295     seq.addSequenceFeature(new SequenceFeature("Type1", "Nonpos", 0, 0, 1f,
296             null));
297     sb.setLength(0);
298     sar.createSequenceAnnotationReport(sb, seq, true, true, null);
299     String expected = "<i>SeqDesc<br/>Type1 ; Nonpos Score=1.0</i>";
300     assertEquals(expected, sb.toString());
301
302     /*
303      * non-positional features not wanted
304      */
305     sb.setLength(0);
306     sar.createSequenceAnnotationReport(sb, seq, true, false, null);
307     assertEquals("<i>SeqDesc</i>", sb.toString());
308
309     /*
310      * add non-pos feature with score inside min-max range for feature type
311      * minmax holds { [positionalMin, positionalMax], [nonPosMin, nonPosMax] }
312      * score is only appended for positional features so ignored here!
313      * minMax are not recorded for non-positional features
314      */
315     seq.addSequenceFeature(new SequenceFeature("Metal", "Desc", 0, 0, 5f,
316             null));
317
318     FeatureRendererModel fr = new FeatureRenderer(null);
319     Map<String, float[][]> minmax = fr.getMinMax();
320     minmax.put("Metal", new float[][] { null, new float[] { 2f, 5f } });
321
322     sb.setLength(0);
323     sar.createSequenceAnnotationReport(sb, seq, true, true, fr);
324     expected = "<i>SeqDesc<br/>Metal ; Desc<br/>Type1 ; Nonpos</i>";
325     assertEquals(expected, sb.toString());
326     
327     /*
328      * 'linkonly' features are ignored; this is obsolete, as linkonly
329      * is only set by DasSequenceFetcher, and DAS is history
330      */
331     SequenceFeature sf = new SequenceFeature("Metal", "Desc", 0, 0, 5f,
332             null);
333     sf.setValue("linkonly", Boolean.TRUE);
334     seq.addSequenceFeature(sf);
335     sb.setLength(0);
336     sar.createSequenceAnnotationReport(sb, seq, true, true, fr);
337     assertEquals(expected, sb.toString()); // unchanged!
338
339     /*
340      * 'clinical_significance' attribute is only included in description 
341      * when used for feature colouring
342      */
343     SequenceFeature sf2 = new SequenceFeature("Variant", "Havana", 0, 0,
344             5f, null);
345     sf2.setValue(GffConstants.CLINICAL_SIGNIFICANCE, "benign");
346     seq.addSequenceFeature(sf2);
347     sb.setLength(0);
348     sar.createSequenceAnnotationReport(sb, seq, true, true, fr);
349     expected = "<i>SeqDesc<br/>Metal ; Desc<br/>Type1 ; Nonpos<br/>Variant ; Havana</i>";
350     assertEquals(expected, sb.toString());
351
352     /*
353      * add dbrefs
354      */
355     seq.addDBRef(new DBRefEntry("PDB", "0", "3iu1"));
356     seq.addDBRef(new DBRefEntry("Uniprot", "1", "P30419"));
357
358     // with showDbRefs = false
359     sb.setLength(0);
360     sar.createSequenceAnnotationReport(sb, seq, false, true, fr);
361     assertEquals(expected, sb.toString()); // unchanged
362
363     // with showDbRefs = true, colour Variant features by clinical_significance
364     sb.setLength(0);
365     FeatureColourI fc = new FeatureColour(null, Color.green, Color.pink,
366             null, 2, 3);
367     fc.setAttributeName("clinical_significance");
368     fr.setColour("Variant", fc);
369     sar.createSequenceAnnotationReport(sb, seq, true, true, fr);
370     expected = "<i>SeqDesc<br/>UNIPROT P30419<br/>PDB 3iu1<br/>Metal ; Desc<br/>"
371             + "Type1 ; Nonpos<br/>Variant ; Havana; clinical_significance=benign</i>";
372     assertEquals(expected, sb.toString());
373     // with showNonPositionalFeatures = false
374     sb.setLength(0);
375     sar.createSequenceAnnotationReport(sb, seq, true, false, fr);
376     expected = "<i>SeqDesc<br/>UNIPROT P30419<br/>PDB 3iu1</i>";
377     assertEquals(expected, sb.toString());
378
379     /*
380      * long feature description is truncated with ellipsis
381      */
382     sb.setLength(0);
383     sf2.setDescription(
384             "This is a very long description which should be truncated");
385     sar.createSequenceAnnotationReport(sb, seq, false, true, fr);
386     expected = "<i>SeqDesc<br/>Metal ; Desc<br/>Type1 ; Nonpos<br/>Variant ; This is a very long description which sh...; clinical_significance=benign</i>";
387     assertEquals(expected, sb.toString());
388
389     // see other tests for treatment of status and html
390   }
391
392   /**
393    * Test that exercises an abbreviated sequence details report, with ellipsis
394    * where there are more than 40 different sources, or more than 4 dbrefs for a
395    * single source
396    */
397   @Test(groups = "Functional")
398   public void testCreateSequenceAnnotationReport_withEllipsis()
399   {
400     SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
401     StringBuilder sb = new StringBuilder();
402   
403     SequenceI seq = new Sequence("s1", "ABC");
404
405     int maxSources = (int) PA.getValue(sar, "MAX_SOURCES");
406     for (int i = 0; i <= maxSources; i++)
407     {
408       seq.addDBRef(new DBRefEntry("PDB" + i, "0", "3iu1"));
409     }
410     
411     int maxRefs = (int) PA.getValue(sar, "MAX_REFS_PER_SOURCE");
412     for (int i = 0; i <= maxRefs; i++)
413     {
414       seq.addDBRef(new DBRefEntry("Uniprot", "0", "P3041" + i));
415     }
416   
417     sar.createSequenceAnnotationReport(sb, seq, true, true, null, true);
418     String report = sb.toString();
419     assertTrue(report
420             .startsWith(
421                     "<i><br/>UNIPROT P30410, P30411, P30412, P30413,...<br/>PDB0 3iu1"));
422     assertTrue(report
423             .endsWith(
424                     "<br/>PDB7 3iu1<br/>PDB8,...<br/>(Output Sequence Details to list all database references)</i>"));
425   }
426 }