Merge branch 'Jalview-JS/develop' into merge_js_develop
[jalview.git] / test / jalview / gui / PopupMenuTest.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.gui;
22
23 import static jalview.util.UrlConstants.DB_ACCESSION;
24 import static jalview.util.UrlConstants.SEQUENCE_ID;
25 import static org.testng.AssertJUnit.assertEquals;
26 import static org.testng.AssertJUnit.assertFalse;
27 import static org.testng.AssertJUnit.assertNotNull;
28 import static org.testng.AssertJUnit.assertNull;
29 import static org.testng.AssertJUnit.assertTrue;
30
31 import java.awt.Component;
32 import java.awt.Container;
33 import java.io.IOException;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.Iterator;
37 import java.util.List;
38
39 import javax.swing.JMenu;
40 import javax.swing.JMenuItem;
41 import javax.swing.JPopupMenu;
42 import javax.swing.JSeparator;
43
44 import org.testng.annotations.BeforeClass;
45 import org.testng.annotations.BeforeMethod;
46 import org.testng.annotations.Test;
47
48 import jalview.bin.Cache;
49 import jalview.datamodel.AlignmentAnnotation;
50 import jalview.datamodel.AlignmentI;
51 import jalview.datamodel.Annotation;
52 import jalview.datamodel.ColumnSelection;
53 import jalview.datamodel.DBRefEntry;
54 import jalview.datamodel.DBRefSource;
55 import jalview.datamodel.HiddenColumns;
56 import jalview.datamodel.Sequence;
57 import jalview.datamodel.SequenceFeature;
58 import jalview.datamodel.SequenceGroup;
59 import jalview.datamodel.SequenceI;
60 import jalview.io.DataSourceType;
61 import jalview.io.FileFormat;
62 import jalview.io.FormatAdapter;
63 import jalview.urls.api.UrlProviderFactoryI;
64 import jalview.urls.desktop.DesktopUrlProviderFactory;
65 import jalview.util.MessageManager;
66 import jalview.util.UrlConstants;
67
68 public class PopupMenuTest
69 {
70
71   @BeforeClass(alwaysRun = true)
72   public void setUpJvOptionPane()
73   {
74     JvOptionPane.setInteractiveMode(false);
75     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
76   }
77
78   // 4 sequences x 13 positions
79   final static String TEST_DATA = ">FER_CAPAA Ferredoxin\n"
80           + "TIETHKEAELVG-\n"
81           + ">FER_CAPAN Ferredoxin, chloroplast precursor\n"
82           + "TIETHKEAELVG-\n"
83           + ">FER1_SOLLC Ferredoxin-1, chloroplast precursor\n"
84           + "TIETHKEEELTA-\n" + ">Q93XJ9_SOLTU Ferredoxin I precursor\n"
85           + "TIETHKEEELTA-\n";
86
87   AlignmentI alignment;
88
89   AlignmentPanel parentPanel;
90
91   PopupMenu testee = null;
92
93   @BeforeMethod(alwaysRun = true)
94   public void setUp() throws IOException
95   {
96     Cache.loadProperties("test/jalview/io/testProps.jvprops");
97     Cache.initLogger();
98
99     String inMenuString = ("EMBL-EBI Search | http://www.ebi.ac.uk/ebisearch/search.ebi?db=allebi&query=$"
100             + SEQUENCE_ID
101             + "$"
102             + "|"
103             + "UNIPROT | http://www.uniprot.org/uniprot/$" + DB_ACCESSION + "$")
104             + "|"
105             + ("INTERPRO | http://www.ebi.ac.uk/interpro/entry/$"
106                     + DB_ACCESSION + "$")
107             + "|"
108             +
109             // Gene3D entry tests for case (in)sensitivity
110             ("Gene3D | http://gene3d.biochem.ucl.ac.uk/Gene3D/search?sterm=$"
111                     + DB_ACCESSION + "$&mode=protein");
112
113     UrlProviderFactoryI factory = new DesktopUrlProviderFactory(
114             UrlConstants.DEFAULT_LABEL, inMenuString, "");
115     Preferences.sequenceUrlLinks = factory.createUrlProvider();
116
117     alignment = new FormatAdapter().readFile(TEST_DATA,
118             DataSourceType.PASTE, FileFormat.Fasta);
119     AlignFrame af = new AlignFrame(alignment, 700, 500);
120     parentPanel = new AlignmentPanel(af, af.getViewport());
121     testee = new PopupMenu(parentPanel, alignment.getSequenceAt(0), null);
122     int i = 0;
123     for (SequenceI seq : alignment.getSequences())
124     {
125       final AlignmentAnnotation annotation = new AlignmentAnnotation(
126               "label" + i, "desc" + i, i);
127       annotation.setCalcId("calcId" + i);
128       seq.addAlignmentAnnotation(annotation);
129       annotation.setSequenceRef(seq);
130     }
131   }
132
133   @Test(groups = { "Functional" })
134   public void testConfigureReferenceAnnotationsMenu_noSequenceSelected()
135   {
136     JMenuItem menu = new JMenuItem();
137     List<SequenceI> seqs = new ArrayList<>();
138     testee.configureReferenceAnnotationsMenu(menu, seqs);
139     assertFalse(menu.isEnabled());
140     // now try null list
141     menu.setEnabled(true);
142     testee.configureReferenceAnnotationsMenu(menu, null);
143     assertFalse(menu.isEnabled());
144   }
145
146   /**
147    * Test building the 'add reference annotations' menu for the case where there
148    * are no reference annotations to add to the alignment. The menu item should
149    * be disabled.
150    */
151   @Test(groups = { "Functional" })
152   public void testConfigureReferenceAnnotationsMenu_noReferenceAnnotations()
153   {
154     JMenuItem menu = new JMenuItem();
155
156     /*
157      * Initial state is that sequences have annotations, and have dataset
158      * sequences, but the dataset sequences have no annotations. Hence nothing
159      * to add.
160      */
161     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
162
163     testee.configureReferenceAnnotationsMenu(menu, seqs);
164     assertFalse(menu.isEnabled());
165   }
166
167   /**
168    * Test building the 'add reference annotations' menu for the case where all
169    * reference annotations are already on the alignment. The menu item should be
170    * disabled.
171    */
172   @Test(groups = { "Functional" })
173   public void testConfigureReferenceAnnotationsMenu_alreadyAdded()
174   {
175     JMenuItem menu = new JMenuItem();
176     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
177
178     // make up new annotations and add to dataset sequences, sequences and
179     // alignment
180     attachReferenceAnnotations(seqs, true, true);
181
182     testee.configureReferenceAnnotationsMenu(menu, seqs);
183     assertFalse(menu.isEnabled());
184   }
185
186   /**
187    * Test building the 'add reference annotations' menu for the case where
188    * several reference annotations are on the dataset but not on the sequences.
189    * The menu item should be enabled, and acquire a tooltip which lists the
190    * annotation sources (calcIds) and type (labels).
191    */
192   @Test(groups = { "Functional" })
193   public void testConfigureReferenceAnnotationsMenu()
194   {
195     JMenuItem menu = new JMenuItem();
196     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
197
198     // make up new annotations and add to dataset sequences
199     attachReferenceAnnotations(seqs, false, false);
200
201     testee.configureReferenceAnnotationsMenu(menu, seqs);
202     assertTrue(menu.isEnabled());
203     String s = MessageManager.getString("label.add_annotations_for");
204 //    String expected = "<html><style> div.ttip {width:350px;white-space:pre-wrap;padding:2px;overflow-wrap:break-word;}</style>"
205 //            + "<div class=\"ttip\">" + s
206 //            + "<br/>Jmol/secondary structure<br/>PDB/Temp </div></html>";
207     String expected = "<html>" + s + "<br>Jmol/secondary structure<br>PDB/Temp</html>";
208     assertEquals(expected, menu.getToolTipText());
209   }
210
211   /**
212    * Test building the 'add reference annotations' menu for the case where
213    * several reference annotations are on the dataset and the sequences but not
214    * on the alignment. The menu item should be enabled, and acquire a tooltip
215    * which lists the annotation sources (calcIds) and type (labels).
216    */
217   @Test(groups = { "Functional" })
218   public void testConfigureReferenceAnnotationsMenu_notOnAlignment()
219   {
220     JMenuItem menu = new JMenuItem();
221     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
222
223     // make up new annotations and add to dataset sequences and sequences
224     attachReferenceAnnotations(seqs, true, false);
225
226     testee.configureReferenceAnnotationsMenu(menu, seqs);
227     assertTrue(menu.isEnabled());
228     String s = MessageManager.getString("label.add_annotations_for");
229 //    String expected = "<html><style> div.ttip {width:350px;white-space:pre-wrap;padding:2px;overflow-wrap:break-word;}</style>"
230 //            + "<div class=\"ttip\">" + s
231 //            + "<br/>Jmol/secondary structure<br/>PDB/Temp</html>";
232     String expected = "<html>" + s
233             + "<br>Jmol/secondary structure<br>PDB/Temp</html>";
234     s = menu.getToolTipText();
235     assertEquals(expected, s);
236   }
237
238   /**
239    * Generate annotations and add to dataset sequences and (optionally)
240    * sequences and/or alignment
241    * 
242    * @param seqs
243    * @param addToSequence
244    * @param addToAlignment
245    */
246   private void attachReferenceAnnotations(List<SequenceI> seqs,
247           boolean addToSequence, boolean addToAlignment)
248   {
249     // PDB.secondary structure on Sequence0
250     AlignmentAnnotation annotation = new AlignmentAnnotation(
251             "secondary structure", "", 0);
252     annotation.annotations = new Annotation[] { new Annotation(2f) };
253     annotation.setCalcId("PDB");
254     seqs.get(0).getDatasetSequence().addAlignmentAnnotation(annotation);
255     if (addToSequence)
256     {
257       seqs.get(0).addAlignmentAnnotation(annotation);
258     }
259     if (addToAlignment)
260     {
261       this.alignment.addAnnotation(annotation);
262     }
263
264     // PDB.Temp on Sequence1
265     annotation = new AlignmentAnnotation("Temp", "", 0);
266     annotation.setCalcId("PDB");
267     annotation.annotations = new Annotation[] { new Annotation(2f) };
268     seqs.get(1).getDatasetSequence().addAlignmentAnnotation(annotation);
269     if (addToSequence)
270     {
271       seqs.get(1).addAlignmentAnnotation(annotation);
272     }
273     if (addToAlignment)
274     {
275       this.alignment.addAnnotation(annotation);
276     }
277
278     // JMOL.secondary structure on Sequence0
279     annotation = new AlignmentAnnotation("secondary structure", "", 0);
280     annotation.setCalcId("Jmol");
281     annotation.annotations = new Annotation[] { new Annotation(2f) };
282     seqs.get(0).getDatasetSequence().addAlignmentAnnotation(annotation);
283     if (addToSequence)
284     {
285       seqs.get(0).addAlignmentAnnotation(annotation);
286     }
287     if (addToAlignment)
288     {
289       this.alignment.addAnnotation(annotation);
290     }
291   }
292
293   /**
294    * Test building the 'add reference annotations' menu for the case where there
295    * are two alignment views:
296    * <ul>
297    * <li>in one view, reference annotations have been added (are on the
298    * datasets, sequences and alignment)</li>
299    * <li>in the current view, reference annotations are on the dataset and
300    * sequence, but not the alignment</li>
301    * </ul>
302    * The menu item should be enabled, and acquire a tooltip which lists the
303    * annotation sources (calcIds) and type (labels).
304    */
305   @Test(groups = { "Functional" })
306   public void testConfigureReferenceAnnotationsMenu_twoViews()
307   {
308   }
309
310   /**
311    * Test for building menu options including 'show' and 'hide' annotation
312    * types.
313    */
314   @Test(groups = { "Functional" })
315   public void testBuildAnnotationTypesMenus()
316   {
317     JMenu showMenu = new JMenu();
318     JMenu hideMenu = new JMenu();
319     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
320
321     // make up new annotations and add to sequences and to the alignment
322
323     // PDB.secondary structure on Sequence0
324     AlignmentAnnotation annotation = new AlignmentAnnotation(
325             "secondary structure", "", new Annotation[] {});
326     annotation.setCalcId("PDB");
327     annotation.visible = true;
328     seqs.get(0).addAlignmentAnnotation(annotation);
329     parentPanel.getAlignment().addAnnotation(annotation);
330
331     // JMOL.secondary structure on Sequence0 - hidden
332     annotation = new AlignmentAnnotation("secondary structure", "",
333             new Annotation[] {});
334     annotation.setCalcId("JMOL");
335     annotation.visible = false;
336     seqs.get(0).addAlignmentAnnotation(annotation);
337     parentPanel.getAlignment().addAnnotation(annotation);
338
339     // Jpred.SSP on Sequence0 - hidden
340     annotation = new AlignmentAnnotation("SSP", "", new Annotation[] {});
341     annotation.setCalcId("JPred");
342     annotation.visible = false;
343     seqs.get(0).addAlignmentAnnotation(annotation);
344     parentPanel.getAlignment().addAnnotation(annotation);
345
346     // PDB.Temp on Sequence1
347     annotation = new AlignmentAnnotation("Temp", "", new Annotation[] {});
348     annotation.setCalcId("PDB");
349     annotation.visible = true;
350     seqs.get(1).addAlignmentAnnotation(annotation);
351     parentPanel.getAlignment().addAnnotation(annotation);
352
353     /*
354      * Expect menu options to show "secondary structure" and "SSP", and to hide
355      * "secondary structure" and "Temp". Tooltip should be calcId.
356      */
357     testee.buildAnnotationTypesMenus(showMenu, hideMenu, seqs);
358
359     assertTrue(showMenu.isEnabled());
360     assertTrue(hideMenu.isEnabled());
361
362     Component[] showOptions = showMenu.getMenuComponents();
363     Component[] hideOptions = hideMenu.getMenuComponents();
364
365     assertEquals(4, showOptions.length); // includes 'All' and separator
366     assertEquals(4, hideOptions.length);
367     String all = MessageManager.getString("label.all");
368     assertEquals(all, ((JMenuItem) showOptions[0]).getText());
369     assertTrue(showOptions[1] instanceof JPopupMenu.Separator);
370     assertEquals(JSeparator.HORIZONTAL,
371             ((JSeparator) showOptions[1]).getOrientation());
372     assertEquals("secondary structure",
373             ((JMenuItem) showOptions[2]).getText());
374     assertEquals("JMOL", ((JMenuItem) showOptions[2]).getToolTipText());
375     assertEquals("SSP", ((JMenuItem) showOptions[3]).getText());
376     assertEquals("JPred", ((JMenuItem) showOptions[3]).getToolTipText());
377
378     assertEquals(all, ((JMenuItem) hideOptions[0]).getText());
379     assertTrue(hideOptions[1] instanceof JPopupMenu.Separator);
380     assertEquals(JSeparator.HORIZONTAL,
381             ((JSeparator) hideOptions[1]).getOrientation());
382     assertEquals("secondary structure",
383             ((JMenuItem) hideOptions[2]).getText());
384     assertEquals("PDB", ((JMenuItem) hideOptions[2]).getToolTipText());
385     assertEquals("Temp", ((JMenuItem) hideOptions[3]).getText());
386     assertEquals("PDB", ((JMenuItem) hideOptions[3]).getToolTipText());
387   }
388
389   /**
390    * Test for building menu options with only 'hide' annotation types enabled.
391    */
392   @Test(groups = { "Functional" })
393   public void testBuildAnnotationTypesMenus_showDisabled()
394   {
395     JMenu showMenu = new JMenu();
396     JMenu hideMenu = new JMenu();
397     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
398
399     // make up new annotations and add to sequences and to the alignment
400
401     // PDB.secondary structure on Sequence0
402     AlignmentAnnotation annotation = new AlignmentAnnotation(
403             "secondary structure", "", new Annotation[] {});
404     annotation.setCalcId("PDB");
405     annotation.visible = true;
406     seqs.get(0).addAlignmentAnnotation(annotation);
407     parentPanel.getAlignment().addAnnotation(annotation);
408
409     // PDB.Temp on Sequence1
410     annotation = new AlignmentAnnotation("Temp", "", new Annotation[] {});
411     annotation.setCalcId("PDB");
412     annotation.visible = true;
413     seqs.get(1).addAlignmentAnnotation(annotation);
414     parentPanel.getAlignment().addAnnotation(annotation);
415
416     /*
417      * Expect menu options to hide "secondary structure" and "Temp". Tooltip
418      * should be calcId. 'Show' menu should be disabled.
419      */
420     testee.buildAnnotationTypesMenus(showMenu, hideMenu, seqs);
421
422     assertFalse(showMenu.isEnabled());
423     assertTrue(hideMenu.isEnabled());
424
425     Component[] showOptions = showMenu.getMenuComponents();
426     Component[] hideOptions = hideMenu.getMenuComponents();
427
428     assertEquals(2, showOptions.length); // includes 'All' and separator
429     assertEquals(4, hideOptions.length);
430     String all = MessageManager.getString("label.all");
431     assertEquals(all, ((JMenuItem) showOptions[0]).getText());
432     assertTrue(showOptions[1] instanceof JPopupMenu.Separator);
433     assertEquals(JSeparator.HORIZONTAL,
434             ((JSeparator) showOptions[1]).getOrientation());
435
436     assertEquals(all, ((JMenuItem) hideOptions[0]).getText());
437     assertTrue(hideOptions[1] instanceof JPopupMenu.Separator);
438     assertEquals(JSeparator.HORIZONTAL,
439             ((JSeparator) hideOptions[1]).getOrientation());
440     assertEquals("secondary structure",
441             ((JMenuItem) hideOptions[2]).getText());
442     assertEquals("PDB", ((JMenuItem) hideOptions[2]).getToolTipText());
443     assertEquals("Temp", ((JMenuItem) hideOptions[3]).getText());
444     assertEquals("PDB", ((JMenuItem) hideOptions[3]).getToolTipText());
445   }
446
447   /**
448    * Test for building menu options with only 'show' annotation types enabled.
449    */
450   @Test(groups = { "Functional" })
451   public void testBuildAnnotationTypesMenus_hideDisabled()
452   {
453     JMenu showMenu = new JMenu();
454     JMenu hideMenu = new JMenu();
455     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
456
457     // make up new annotations and add to sequences and to the alignment
458
459     // PDB.secondary structure on Sequence0
460     AlignmentAnnotation annotation = new AlignmentAnnotation(
461             "secondary structure", "", new Annotation[] {});
462     annotation.setCalcId("PDB");
463     annotation.visible = false;
464     seqs.get(0).addAlignmentAnnotation(annotation);
465     parentPanel.getAlignment().addAnnotation(annotation);
466
467     // PDB.Temp on Sequence1
468     annotation = new AlignmentAnnotation("Temp", "", new Annotation[] {});
469     annotation.setCalcId("PDB2");
470     annotation.visible = false;
471     seqs.get(1).addAlignmentAnnotation(annotation);
472     parentPanel.getAlignment().addAnnotation(annotation);
473
474     /*
475      * Expect menu options to show "secondary structure" and "Temp". Tooltip
476      * should be calcId. 'hide' menu should be disabled.
477      */
478     testee.buildAnnotationTypesMenus(showMenu, hideMenu, seqs);
479
480     assertTrue(showMenu.isEnabled());
481     assertFalse(hideMenu.isEnabled());
482
483     Component[] showOptions = showMenu.getMenuComponents();
484     Component[] hideOptions = hideMenu.getMenuComponents();
485
486     assertEquals(4, showOptions.length); // includes 'All' and separator
487     assertEquals(2, hideOptions.length);
488     String all = MessageManager.getString("label.all");
489     assertEquals(all, ((JMenuItem) showOptions[0]).getText());
490     assertTrue(showOptions[1] instanceof JPopupMenu.Separator);
491     assertEquals(JSeparator.HORIZONTAL,
492             ((JSeparator) showOptions[1]).getOrientation());
493     assertEquals("secondary structure",
494             ((JMenuItem) showOptions[2]).getText());
495     assertEquals("PDB", ((JMenuItem) showOptions[2]).getToolTipText());
496     assertEquals("Temp", ((JMenuItem) showOptions[3]).getText());
497     assertEquals("PDB2", ((JMenuItem) showOptions[3]).getToolTipText());
498
499     assertEquals(all, ((JMenuItem) hideOptions[0]).getText());
500     assertTrue(hideOptions[1] instanceof JPopupMenu.Separator);
501     assertEquals(JSeparator.HORIZONTAL,
502             ((JSeparator) hideOptions[1]).getOrientation());
503   }
504
505   /**
506    * Test for adding sequence id, dbref and feature links
507    */
508   @Test(groups = { "Functional" })
509   public void testBuildLinkMenu()
510   {
511     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
512     final SequenceI seq0 = seqs.get(0);
513     final SequenceI seq1 = seqs.get(1);
514     final List<SequenceFeature> noFeatures = Collections
515             .<SequenceFeature> emptyList();
516     final String linkText = MessageManager.getString("action.link");
517
518     seq0.addDBRef(new DBRefEntry(DBRefSource.UNIPROT, "1", "P83527"));
519     seq0.addDBRef(new DBRefEntry("INTERPRO", "1", "IPR001041"));
520     seq0.addDBRef(new DBRefEntry("INTERPRO", "1", "IPR012675"));
521     seq0.addDBRef(new DBRefEntry("INTERPRO", "1", "IPR006058"));
522     seq1.addDBRef(new DBRefEntry(DBRefSource.UNIPROT, "1", "Q9ZTS2"));
523     seq1.addDBRef(new DBRefEntry("GENE3D", "1", "3.10.20.30"));
524     
525     /*
526      * check the Link Menu for the first sequence
527      */
528     JMenu linkMenu = PopupMenu.buildLinkMenu(seq0, noFeatures);
529     assertEquals(linkText, linkMenu.getText());
530     Component[] linkItems = linkMenu.getMenuComponents();
531     
532     /*
533      * menu items are ordered: SEQUENCE_ID search first, then dbrefs in order
534      * of database name (and within that by order of dbref addition)
535      */
536     assertEquals(5, linkItems.length);
537     assertEquals("EMBL-EBI Search", ((JMenuItem) linkItems[0]).getText());
538     assertEquals("INTERPRO|IPR001041",
539             ((JMenuItem) linkItems[1]).getText());
540     assertEquals("INTERPRO|IPR012675",
541             ((JMenuItem) linkItems[2]).getText());
542     assertEquals("INTERPRO|IPR006058",
543             ((JMenuItem) linkItems[3]).getText());
544     assertEquals("UNIPROT|P83527", ((JMenuItem) linkItems[4]).getText());
545
546     /*
547      * check the Link Menu for the second sequence
548      * note dbref GENE3D is matched to link Gene3D, the latter is displayed
549      */
550     linkMenu = PopupMenu.buildLinkMenu(seq1, noFeatures);
551     linkItems = linkMenu.getMenuComponents();
552     assertEquals(3, linkItems.length);
553     assertEquals("EMBL-EBI Search", ((JMenuItem) linkItems[0]).getText());
554     assertEquals("Gene3D|3.10.20.30", ((JMenuItem) linkItems[1]).getText());
555     assertEquals("UNIPROT|Q9ZTS2", ((JMenuItem) linkItems[2]).getText());
556
557     /*
558      * if there are no valid links the Links submenu is still shown, but
559      * reduced to the EMBL-EBI lookup only (inserted by 
560      * CustomUrlProvider.choosePrimaryUrl())
561      */
562     String unmatched = "NOMATCH|http://www.uniprot.org/uniprot/$"
563             + DB_ACCESSION + "$";
564     UrlProviderFactoryI factory = new DesktopUrlProviderFactory(null,
565             unmatched, "");
566     Preferences.sequenceUrlLinks = factory.createUrlProvider();
567
568     linkMenu = PopupMenu.buildLinkMenu(seq1, noFeatures);
569     linkItems = linkMenu.getMenuComponents();
570     assertEquals(1, linkItems.length);
571     assertEquals("EMBL-EBI Search", ((JMenuItem) linkItems[0]).getText());
572
573     /*
574      * if sequence is null, only feature links are shown (alignment popup submenu)
575      */
576     linkMenu = PopupMenu.buildLinkMenu(null, noFeatures);
577     linkItems = linkMenu.getMenuComponents();
578     assertEquals(0, linkItems.length);
579
580     List<SequenceFeature> features = new ArrayList<>();
581     SequenceFeature sf = new SequenceFeature("type", "desc", 1, 20, null);
582     features.add(sf);
583     linkMenu = PopupMenu.buildLinkMenu(null, features);
584     linkItems = linkMenu.getMenuComponents();
585     assertEquals(0, linkItems.length); // feature has no links
586
587     sf.addLink("Pfam family|http://pfam.xfam.org/family/PF00111");
588     linkMenu = PopupMenu.buildLinkMenu(null, features);
589     linkItems = linkMenu.getMenuComponents();
590     assertEquals(1, linkItems.length);
591     JMenuItem item = (JMenuItem) linkItems[0];
592     assertEquals("Pfam family", item.getText());
593     // ? no way to verify URL, compiled into link's actionListener
594   }
595
596   @Test(groups = { "Functional" })
597   public void testHideInsertions()
598   {
599     // get sequences from the alignment
600     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
601     
602     // add our own seqs to avoid problems with changes to existing sequences
603     // (gap at end of sequences varies depending on how tests are run!)
604     Sequence seqGap1 = new Sequence("GappySeq",
605             "AAAA----AA-AAAAAAA---AAA-----------AAAAAAAAAA--");
606     seqGap1.createDatasetSequence();
607     seqs.add(seqGap1);
608     Sequence seqGap2 = new Sequence("LessGappySeq",
609             "AAAAAA-AAAAA---AAA--AAAAA--AAAAAAA-AAAAAA");
610     seqGap2.createDatasetSequence();
611     seqs.add(seqGap2);
612     Sequence seqGap3 = new Sequence("AnotherGapSeq",
613             "AAAAAA-AAAAAA--AAAAAA-AAAAAAAAAAA---AAAAAAAA");
614     seqGap3.createDatasetSequence();
615     seqs.add(seqGap3);
616     Sequence seqGap4 = new Sequence("NoGaps",
617             "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
618     seqGap4.createDatasetSequence();
619     seqs.add(seqGap4);
620
621     ColumnSelection sel = new ColumnSelection();
622     parentPanel.av.getAlignment().getHiddenColumns()
623             .revealAllHiddenColumns(sel);
624
625     // get the Popup Menu for 7th sequence - no insertions
626     testee = new PopupMenu(parentPanel, seqs.get(7), null);
627     testee.hideInsertions_actionPerformed(null);
628     
629     HiddenColumns hidden = parentPanel.av.getAlignment().getHiddenColumns();
630     Iterator<int[]> it = hidden.iterator();
631     assertFalse(it.hasNext());
632
633     // get the Popup Menu for GappySeq - this time we have insertions
634     testee = new PopupMenu(parentPanel, seqs.get(4), null);
635     testee.hideInsertions_actionPerformed(null);
636     hidden = parentPanel.av.getAlignment().getHiddenColumns();
637     it = hidden.iterator();
638
639     assertTrue(it.hasNext());
640     int[] region = it.next();
641     assertEquals(region[0], 4);
642     assertEquals(region[1], 7);
643
644     assertTrue(it.hasNext());
645     region = it.next();
646     assertEquals(region[0], 10);
647     assertEquals(region[1], 10);
648
649     assertTrue(it.hasNext());
650     region = it.next();
651     assertEquals(region[0], 18);
652     assertEquals(region[1], 20);
653
654     assertTrue(it.hasNext());
655     region = it.next();
656     assertEquals(region[0], 24);
657     assertEquals(region[1], 34);
658
659     assertTrue(it.hasNext());
660     region = it.next();
661     assertEquals(region[0], 45);
662     assertEquals(region[1], 46);
663
664     assertFalse(it.hasNext());
665
666     sel = new ColumnSelection();
667     hidden.revealAllHiddenColumns(sel);
668
669     // make a sequence group and hide insertions within the group
670     SequenceGroup sg = new SequenceGroup();
671     sg.setStartRes(8);
672     sg.setEndRes(42);
673     sg.addSequence(seqGap2, false);
674     sg.addSequence(seqGap3, false);
675     parentPanel.av.setSelectionGroup(sg);
676
677     // hide columns outside and within selection
678     // only hidden columns outside the collection will be retained (unless also
679     // gaps in the selection)
680     hidden.hideColumns(1, 10);
681     hidden.hideColumns(31, 40);
682
683     // get the Popup Menu for LessGappySeq in the sequence group
684     testee = new PopupMenu(parentPanel, seqs.get(5), null);
685     testee.hideInsertions_actionPerformed(null);
686     hidden = parentPanel.av.getAlignment().getHiddenColumns();
687     it = hidden.iterator();
688
689     assertTrue(it.hasNext());
690     region = it.next();
691     assertEquals(region[0], 1);
692     assertEquals(region[1], 7);
693
694     assertTrue(it.hasNext());
695     region = it.next();
696     assertEquals(region[0], 13);
697     assertEquals(region[1], 14);
698
699     assertTrue(it.hasNext());
700     region = it.next();
701     assertEquals(region[0], 34);
702     assertEquals(region[1], 34);
703   }
704
705   @Test(groups = { "Functional" })
706   public void testAddFeatureDetails()
707   {
708     String menuText = MessageManager.getString("label.feature_details");
709
710     /*
711      * with no features, sub-menu should not be created
712      */
713     List<SequenceFeature> features = new ArrayList<>();
714     SequenceI seq = this.alignment.getSequenceAt(0); // FER_CAPAA/1-12
715     testee.addFeatureDetails(features, seq, 10);
716     JMenu menu = findMenu(testee, menuText);
717     assertNull(menu);
718
719     /*
720      * add some features; the menu item text is wrapped in html, and includes
721      * feature type, position, description, group (if not null)
722      */
723     SequenceFeature sf1 = new SequenceFeature("helix", "curly", 2, 6, null);
724     SequenceFeature sf2 = new SequenceFeature("chain", "straight", 1, 1,
725             "uniprot");
726     features.add(sf1);
727     features.add(sf2);
728     testee.addFeatureDetails(features, seq, 10);
729     menu = findMenu(testee, menuText);
730     assertNotNull(menu);
731     assertEquals(2, menu.getItemCount());
732     JMenuItem item = menu.getItem(0);
733     assertEquals("<html>helix 2-6 curly</html>", item.getText());
734     item = menu.getItem(1);
735     assertEquals("<html>chain 1 straight (uniprot)</html>", item.getText());
736
737     /*
738      * long feature descriptions are truncated to 40 characters
739      */
740     sf1.setDescription(
741             "this is a quite extraordinarily long description");
742     testee.remove(menu); // don't create the sub-menu twice
743     testee.addFeatureDetails(features, seq, 10);
744     menu = findMenu(testee, menuText);
745     item = menu.getItem(0);
746     assertEquals(
747             "<html>helix 2-6 this is a quite extraordinarily long des...</html>",
748             item.getText());
749   }
750
751   /**
752    * Returns the first component which is a JMenu with the given text
753    * 
754    * @param c
755    * @param text
756    * @return
757    */
758   private JMenu findMenu(Container c, String text)
759   {
760     for (int i = 0; i < c.getComponentCount(); i++)
761     {
762       Component comp = c.getComponent(i);
763       if ((comp instanceof JMenu) && ((JMenu) comp).getText().equals(text))
764       {
765         return (JMenu) comp;
766       }
767     }
768     return null;
769   }
770
771   @Test(groups = { "Functional" })
772   public void testAddFeatureDetails_linkedFeatures()
773   {
774     // todo tests that verify menu items for complement features
775   }
776 }