28b5728dd4e629c3c6674bd94c241c0def7ac433
[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     assertEquals(expected, menu.getToolTipText());
208   }
209
210   /**
211    * Test building the 'add reference annotations' menu for the case where
212    * several reference annotations are on the dataset and the sequences but not
213    * on the alignment. The menu item should be enabled, and acquire a tooltip
214    * which lists the annotation sources (calcIds) and type (labels).
215    */
216   @Test(groups = { "Functional" })
217   public void testConfigureReferenceAnnotationsMenu_notOnAlignment()
218   {
219     JMenuItem menu = new JMenuItem();
220     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
221
222     // make up new annotations and add to dataset sequences and sequences
223     attachReferenceAnnotations(seqs, true, false);
224
225     testee.configureReferenceAnnotationsMenu(menu, seqs);
226     assertTrue(menu.isEnabled());
227     String s = MessageManager.getString("label.add_annotations_for");
228     String expected = "<html><style> div.ttip {width:350px;white-space:pre-wrap;padding:2px;overflow-wrap:break-word;}</style>"
229             + "<div class=\"ttip\">" + s
230             + "<br/>Jmol/secondary structure<br/>PDB/Temp </div></html>";
231     assertEquals(expected, menu.getToolTipText());
232   }
233
234   /**
235    * Generate annotations and add to dataset sequences and (optionally)
236    * sequences and/or alignment
237    * 
238    * @param seqs
239    * @param addToSequence
240    * @param addToAlignment
241    */
242   private void attachReferenceAnnotations(List<SequenceI> seqs,
243           boolean addToSequence, boolean addToAlignment)
244   {
245     // PDB.secondary structure on Sequence0
246     AlignmentAnnotation annotation = new AlignmentAnnotation(
247             "secondary structure", "", 0);
248     annotation.annotations = new Annotation[] { new Annotation(2f) };
249     annotation.setCalcId("PDB");
250     seqs.get(0).getDatasetSequence().addAlignmentAnnotation(annotation);
251     if (addToSequence)
252     {
253       seqs.get(0).addAlignmentAnnotation(annotation);
254     }
255     if (addToAlignment)
256     {
257       this.alignment.addAnnotation(annotation);
258     }
259
260     // PDB.Temp on Sequence1
261     annotation = new AlignmentAnnotation("Temp", "", 0);
262     annotation.setCalcId("PDB");
263     annotation.annotations = new Annotation[] { new Annotation(2f) };
264     seqs.get(1).getDatasetSequence().addAlignmentAnnotation(annotation);
265     if (addToSequence)
266     {
267       seqs.get(1).addAlignmentAnnotation(annotation);
268     }
269     if (addToAlignment)
270     {
271       this.alignment.addAnnotation(annotation);
272     }
273
274     // JMOL.secondary structure on Sequence0
275     annotation = new AlignmentAnnotation("secondary structure", "", 0);
276     annotation.setCalcId("Jmol");
277     annotation.annotations = new Annotation[] { new Annotation(2f) };
278     seqs.get(0).getDatasetSequence().addAlignmentAnnotation(annotation);
279     if (addToSequence)
280     {
281       seqs.get(0).addAlignmentAnnotation(annotation);
282     }
283     if (addToAlignment)
284     {
285       this.alignment.addAnnotation(annotation);
286     }
287   }
288
289   /**
290    * Test building the 'add reference annotations' menu for the case where there
291    * are two alignment views:
292    * <ul>
293    * <li>in one view, reference annotations have been added (are on the
294    * datasets, sequences and alignment)</li>
295    * <li>in the current view, reference annotations are on the dataset and
296    * sequence, but not the alignment</li>
297    * </ul>
298    * The menu item should be enabled, and acquire a tooltip which lists the
299    * annotation sources (calcIds) and type (labels).
300    */
301   @Test(groups = { "Functional" })
302   public void testConfigureReferenceAnnotationsMenu_twoViews()
303   {
304   }
305
306   /**
307    * Test for building menu options including 'show' and 'hide' annotation
308    * types.
309    */
310   @Test(groups = { "Functional" })
311   public void testBuildAnnotationTypesMenus()
312   {
313     JMenu showMenu = new JMenu();
314     JMenu hideMenu = new JMenu();
315     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
316
317     // make up new annotations and add to sequences and to the alignment
318
319     // PDB.secondary structure on Sequence0
320     AlignmentAnnotation annotation = new AlignmentAnnotation(
321             "secondary structure", "", new Annotation[] {});
322     annotation.setCalcId("PDB");
323     annotation.visible = true;
324     seqs.get(0).addAlignmentAnnotation(annotation);
325     parentPanel.getAlignment().addAnnotation(annotation);
326
327     // JMOL.secondary structure on Sequence0 - hidden
328     annotation = new AlignmentAnnotation("secondary structure", "",
329             new Annotation[] {});
330     annotation.setCalcId("JMOL");
331     annotation.visible = false;
332     seqs.get(0).addAlignmentAnnotation(annotation);
333     parentPanel.getAlignment().addAnnotation(annotation);
334
335     // Jpred.SSP on Sequence0 - hidden
336     annotation = new AlignmentAnnotation("SSP", "", new Annotation[] {});
337     annotation.setCalcId("JPred");
338     annotation.visible = false;
339     seqs.get(0).addAlignmentAnnotation(annotation);
340     parentPanel.getAlignment().addAnnotation(annotation);
341
342     // PDB.Temp on Sequence1
343     annotation = new AlignmentAnnotation("Temp", "", new Annotation[] {});
344     annotation.setCalcId("PDB");
345     annotation.visible = true;
346     seqs.get(1).addAlignmentAnnotation(annotation);
347     parentPanel.getAlignment().addAnnotation(annotation);
348
349     /*
350      * Expect menu options to show "secondary structure" and "SSP", and to hide
351      * "secondary structure" and "Temp". Tooltip should be calcId.
352      */
353     testee.buildAnnotationTypesMenus(showMenu, hideMenu, seqs);
354
355     assertTrue(showMenu.isEnabled());
356     assertTrue(hideMenu.isEnabled());
357
358     Component[] showOptions = showMenu.getMenuComponents();
359     Component[] hideOptions = hideMenu.getMenuComponents();
360
361     assertEquals(4, showOptions.length); // includes 'All' and separator
362     assertEquals(4, hideOptions.length);
363     String all = MessageManager.getString("label.all");
364     assertEquals(all, ((JMenuItem) showOptions[0]).getText());
365     assertTrue(showOptions[1] instanceof JPopupMenu.Separator);
366     assertEquals(JSeparator.HORIZONTAL,
367             ((JSeparator) showOptions[1]).getOrientation());
368     assertEquals("secondary structure",
369             ((JMenuItem) showOptions[2]).getText());
370     assertEquals("JMOL", ((JMenuItem) showOptions[2]).getToolTipText());
371     assertEquals("SSP", ((JMenuItem) showOptions[3]).getText());
372     assertEquals("JPred", ((JMenuItem) showOptions[3]).getToolTipText());
373
374     assertEquals(all, ((JMenuItem) hideOptions[0]).getText());
375     assertTrue(hideOptions[1] instanceof JPopupMenu.Separator);
376     assertEquals(JSeparator.HORIZONTAL,
377             ((JSeparator) hideOptions[1]).getOrientation());
378     assertEquals("secondary structure",
379             ((JMenuItem) hideOptions[2]).getText());
380     assertEquals("PDB", ((JMenuItem) hideOptions[2]).getToolTipText());
381     assertEquals("Temp", ((JMenuItem) hideOptions[3]).getText());
382     assertEquals("PDB", ((JMenuItem) hideOptions[3]).getToolTipText());
383   }
384
385   /**
386    * Test for building menu options with only 'hide' annotation types enabled.
387    */
388   @Test(groups = { "Functional" })
389   public void testBuildAnnotationTypesMenus_showDisabled()
390   {
391     JMenu showMenu = new JMenu();
392     JMenu hideMenu = new JMenu();
393     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
394
395     // make up new annotations and add to sequences and to the alignment
396
397     // PDB.secondary structure on Sequence0
398     AlignmentAnnotation annotation = new AlignmentAnnotation(
399             "secondary structure", "", new Annotation[] {});
400     annotation.setCalcId("PDB");
401     annotation.visible = true;
402     seqs.get(0).addAlignmentAnnotation(annotation);
403     parentPanel.getAlignment().addAnnotation(annotation);
404
405     // PDB.Temp on Sequence1
406     annotation = new AlignmentAnnotation("Temp", "", new Annotation[] {});
407     annotation.setCalcId("PDB");
408     annotation.visible = true;
409     seqs.get(1).addAlignmentAnnotation(annotation);
410     parentPanel.getAlignment().addAnnotation(annotation);
411
412     /*
413      * Expect menu options to hide "secondary structure" and "Temp". Tooltip
414      * should be calcId. 'Show' menu should be disabled.
415      */
416     testee.buildAnnotationTypesMenus(showMenu, hideMenu, seqs);
417
418     assertFalse(showMenu.isEnabled());
419     assertTrue(hideMenu.isEnabled());
420
421     Component[] showOptions = showMenu.getMenuComponents();
422     Component[] hideOptions = hideMenu.getMenuComponents();
423
424     assertEquals(2, showOptions.length); // includes 'All' and separator
425     assertEquals(4, hideOptions.length);
426     String all = MessageManager.getString("label.all");
427     assertEquals(all, ((JMenuItem) showOptions[0]).getText());
428     assertTrue(showOptions[1] instanceof JPopupMenu.Separator);
429     assertEquals(JSeparator.HORIZONTAL,
430             ((JSeparator) showOptions[1]).getOrientation());
431
432     assertEquals(all, ((JMenuItem) hideOptions[0]).getText());
433     assertTrue(hideOptions[1] instanceof JPopupMenu.Separator);
434     assertEquals(JSeparator.HORIZONTAL,
435             ((JSeparator) hideOptions[1]).getOrientation());
436     assertEquals("secondary structure",
437             ((JMenuItem) hideOptions[2]).getText());
438     assertEquals("PDB", ((JMenuItem) hideOptions[2]).getToolTipText());
439     assertEquals("Temp", ((JMenuItem) hideOptions[3]).getText());
440     assertEquals("PDB", ((JMenuItem) hideOptions[3]).getToolTipText());
441   }
442
443   /**
444    * Test for building menu options with only 'show' annotation types enabled.
445    */
446   @Test(groups = { "Functional" })
447   public void testBuildAnnotationTypesMenus_hideDisabled()
448   {
449     JMenu showMenu = new JMenu();
450     JMenu hideMenu = new JMenu();
451     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
452
453     // make up new annotations and add to sequences and to the alignment
454
455     // PDB.secondary structure on Sequence0
456     AlignmentAnnotation annotation = new AlignmentAnnotation(
457             "secondary structure", "", new Annotation[] {});
458     annotation.setCalcId("PDB");
459     annotation.visible = false;
460     seqs.get(0).addAlignmentAnnotation(annotation);
461     parentPanel.getAlignment().addAnnotation(annotation);
462
463     // PDB.Temp on Sequence1
464     annotation = new AlignmentAnnotation("Temp", "", new Annotation[] {});
465     annotation.setCalcId("PDB2");
466     annotation.visible = false;
467     seqs.get(1).addAlignmentAnnotation(annotation);
468     parentPanel.getAlignment().addAnnotation(annotation);
469
470     /*
471      * Expect menu options to show "secondary structure" and "Temp". Tooltip
472      * should be calcId. 'hide' menu should be disabled.
473      */
474     testee.buildAnnotationTypesMenus(showMenu, hideMenu, seqs);
475
476     assertTrue(showMenu.isEnabled());
477     assertFalse(hideMenu.isEnabled());
478
479     Component[] showOptions = showMenu.getMenuComponents();
480     Component[] hideOptions = hideMenu.getMenuComponents();
481
482     assertEquals(4, showOptions.length); // includes 'All' and separator
483     assertEquals(2, hideOptions.length);
484     String all = MessageManager.getString("label.all");
485     assertEquals(all, ((JMenuItem) showOptions[0]).getText());
486     assertTrue(showOptions[1] instanceof JPopupMenu.Separator);
487     assertEquals(JSeparator.HORIZONTAL,
488             ((JSeparator) showOptions[1]).getOrientation());
489     assertEquals("secondary structure",
490             ((JMenuItem) showOptions[2]).getText());
491     assertEquals("PDB", ((JMenuItem) showOptions[2]).getToolTipText());
492     assertEquals("Temp", ((JMenuItem) showOptions[3]).getText());
493     assertEquals("PDB2", ((JMenuItem) showOptions[3]).getToolTipText());
494
495     assertEquals(all, ((JMenuItem) hideOptions[0]).getText());
496     assertTrue(hideOptions[1] instanceof JPopupMenu.Separator);
497     assertEquals(JSeparator.HORIZONTAL,
498             ((JSeparator) hideOptions[1]).getOrientation());
499   }
500
501   /**
502    * Test for adding sequence id, dbref and feature links
503    */
504   @Test(groups = { "Functional" })
505   public void testBuildLinkMenu()
506   {
507     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
508     final SequenceI seq0 = seqs.get(0);
509     final SequenceI seq1 = seqs.get(1);
510     final List<SequenceFeature> noFeatures = Collections
511             .<SequenceFeature> emptyList();
512     final String linkText = MessageManager.getString("action.link");
513
514     seq0.addDBRef(new DBRefEntry(DBRefSource.UNIPROT, "1", "P83527"));
515     seq0.addDBRef(new DBRefEntry("INTERPRO", "1", "IPR001041"));
516     seq0.addDBRef(new DBRefEntry("INTERPRO", "1", "IPR012675"));
517     seq0.addDBRef(new DBRefEntry("INTERPRO", "1", "IPR006058"));
518     seq1.addDBRef(new DBRefEntry(DBRefSource.UNIPROT, "1", "Q9ZTS2"));
519     seq1.addDBRef(new DBRefEntry("GENE3D", "1", "3.10.20.30"));
520     
521     /*
522      * check the Link Menu for the first sequence
523      */
524     JMenu linkMenu = PopupMenu.buildLinkMenu(seq0, noFeatures);
525     assertEquals(linkText, linkMenu.getText());
526     Component[] linkItems = linkMenu.getMenuComponents();
527     
528     /*
529      * menu items are ordered: SEQUENCE_ID search first, then dbrefs in order
530      * of database name (and within that by order of dbref addition)
531      */
532     assertEquals(5, linkItems.length);
533     assertEquals("EMBL-EBI Search", ((JMenuItem) linkItems[0]).getText());
534     assertEquals("INTERPRO|IPR001041",
535             ((JMenuItem) linkItems[1]).getText());
536     assertEquals("INTERPRO|IPR012675",
537             ((JMenuItem) linkItems[2]).getText());
538     assertEquals("INTERPRO|IPR006058",
539             ((JMenuItem) linkItems[3]).getText());
540     assertEquals("UNIPROT|P83527", ((JMenuItem) linkItems[4]).getText());
541
542     /*
543      * check the Link Menu for the second sequence
544      * note dbref GENE3D is matched to link Gene3D, the latter is displayed
545      */
546     linkMenu = PopupMenu.buildLinkMenu(seq1, noFeatures);
547     linkItems = linkMenu.getMenuComponents();
548     assertEquals(3, linkItems.length);
549     assertEquals("EMBL-EBI Search", ((JMenuItem) linkItems[0]).getText());
550     assertEquals("Gene3D|3.10.20.30", ((JMenuItem) linkItems[1]).getText());
551     assertEquals("UNIPROT|Q9ZTS2", ((JMenuItem) linkItems[2]).getText());
552
553     /*
554      * if there are no valid links the Links submenu is still shown, but
555      * reduced to the EMBL-EBI lookup only (inserted by 
556      * CustomUrlProvider.choosePrimaryUrl())
557      */
558     String unmatched = "NOMATCH|http://www.uniprot.org/uniprot/$"
559             + DB_ACCESSION + "$";
560     UrlProviderFactoryI factory = new DesktopUrlProviderFactory(null,
561             unmatched, "");
562     Preferences.sequenceUrlLinks = factory.createUrlProvider();
563
564     linkMenu = PopupMenu.buildLinkMenu(seq1, noFeatures);
565     linkItems = linkMenu.getMenuComponents();
566     assertEquals(1, linkItems.length);
567     assertEquals("EMBL-EBI Search", ((JMenuItem) linkItems[0]).getText());
568
569     /*
570      * if sequence is null, only feature links are shown (alignment popup submenu)
571      */
572     linkMenu = PopupMenu.buildLinkMenu(null, noFeatures);
573     linkItems = linkMenu.getMenuComponents();
574     assertEquals(0, linkItems.length);
575
576     List<SequenceFeature> features = new ArrayList<>();
577     SequenceFeature sf = new SequenceFeature("type", "desc", 1, 20, null);
578     features.add(sf);
579     linkMenu = PopupMenu.buildLinkMenu(null, features);
580     linkItems = linkMenu.getMenuComponents();
581     assertEquals(0, linkItems.length); // feature has no links
582
583     sf.addLink("Pfam family|http://pfam.xfam.org/family/PF00111");
584     linkMenu = PopupMenu.buildLinkMenu(null, features);
585     linkItems = linkMenu.getMenuComponents();
586     assertEquals(1, linkItems.length);
587     JMenuItem item = (JMenuItem) linkItems[0];
588     assertEquals("Pfam family", item.getText());
589     // ? no way to verify URL, compiled into link's actionListener
590   }
591
592   @Test(groups = { "Functional" })
593   public void testHideInsertions()
594   {
595     // get sequences from the alignment
596     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
597     
598     // add our own seqs to avoid problems with changes to existing sequences
599     // (gap at end of sequences varies depending on how tests are run!)
600     Sequence seqGap1 = new Sequence("GappySeq",
601             "AAAA----AA-AAAAAAA---AAA-----------AAAAAAAAAA--");
602     seqGap1.createDatasetSequence();
603     seqs.add(seqGap1);
604     Sequence seqGap2 = new Sequence("LessGappySeq",
605             "AAAAAA-AAAAA---AAA--AAAAA--AAAAAAA-AAAAAA");
606     seqGap2.createDatasetSequence();
607     seqs.add(seqGap2);
608     Sequence seqGap3 = new Sequence("AnotherGapSeq",
609             "AAAAAA-AAAAAA--AAAAAA-AAAAAAAAAAA---AAAAAAAA");
610     seqGap3.createDatasetSequence();
611     seqs.add(seqGap3);
612     Sequence seqGap4 = new Sequence("NoGaps",
613             "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
614     seqGap4.createDatasetSequence();
615     seqs.add(seqGap4);
616
617     ColumnSelection sel = new ColumnSelection();
618     parentPanel.av.getAlignment().getHiddenColumns()
619             .revealAllHiddenColumns(sel);
620
621     // get the Popup Menu for 7th sequence - no insertions
622     testee = new PopupMenu(parentPanel, seqs.get(7), null);
623     testee.hideInsertions_actionPerformed(null);
624     
625     HiddenColumns hidden = parentPanel.av.getAlignment().getHiddenColumns();
626     Iterator<int[]> it = hidden.iterator();
627     assertFalse(it.hasNext());
628
629     // get the Popup Menu for GappySeq - this time we have insertions
630     testee = new PopupMenu(parentPanel, seqs.get(4), null);
631     testee.hideInsertions_actionPerformed(null);
632     hidden = parentPanel.av.getAlignment().getHiddenColumns();
633     it = hidden.iterator();
634
635     assertTrue(it.hasNext());
636     int[] region = it.next();
637     assertEquals(region[0], 4);
638     assertEquals(region[1], 7);
639
640     assertTrue(it.hasNext());
641     region = it.next();
642     assertEquals(region[0], 10);
643     assertEquals(region[1], 10);
644
645     assertTrue(it.hasNext());
646     region = it.next();
647     assertEquals(region[0], 18);
648     assertEquals(region[1], 20);
649
650     assertTrue(it.hasNext());
651     region = it.next();
652     assertEquals(region[0], 24);
653     assertEquals(region[1], 34);
654
655     assertTrue(it.hasNext());
656     region = it.next();
657     assertEquals(region[0], 45);
658     assertEquals(region[1], 46);
659
660     assertFalse(it.hasNext());
661
662     sel = new ColumnSelection();
663     hidden.revealAllHiddenColumns(sel);
664
665     // make a sequence group and hide insertions within the group
666     SequenceGroup sg = new SequenceGroup();
667     sg.setStartRes(8);
668     sg.setEndRes(42);
669     sg.addSequence(seqGap2, false);
670     sg.addSequence(seqGap3, false);
671     parentPanel.av.setSelectionGroup(sg);
672
673     // hide columns outside and within selection
674     // only hidden columns outside the collection will be retained (unless also
675     // gaps in the selection)
676     hidden.hideColumns(1, 10);
677     hidden.hideColumns(31, 40);
678
679     // get the Popup Menu for LessGappySeq in the sequence group
680     testee = new PopupMenu(parentPanel, seqs.get(5), null);
681     testee.hideInsertions_actionPerformed(null);
682     hidden = parentPanel.av.getAlignment().getHiddenColumns();
683     it = hidden.iterator();
684
685     assertTrue(it.hasNext());
686     region = it.next();
687     assertEquals(region[0], 1);
688     assertEquals(region[1], 7);
689
690     assertTrue(it.hasNext());
691     region = it.next();
692     assertEquals(region[0], 13);
693     assertEquals(region[1], 14);
694
695     assertTrue(it.hasNext());
696     region = it.next();
697     assertEquals(region[0], 34);
698     assertEquals(region[1], 34);
699   }
700
701   @Test(groups = { "Functional" })
702   public void testAddFeatureDetails()
703   {
704     String menuText = MessageManager.getString("label.feature_details");
705
706     /*
707      * with no features, sub-menu should not be created
708      */
709     List<SequenceFeature> features = new ArrayList<>();
710     SequenceI seq = this.alignment.getSequenceAt(0); // FER_CAPAA/1-12
711     testee.addFeatureDetails(features, seq, 10);
712     JMenu menu = findMenu(testee, menuText);
713     assertNull(menu);
714
715     /*
716      * add some features; the menu item text is wrapped in html, and includes
717      * feature type, position, description, group (if not null)
718      */
719     SequenceFeature sf1 = new SequenceFeature("helix", "curly", 2, 6, null);
720     SequenceFeature sf2 = new SequenceFeature("chain", "straight", 1, 1,
721             "uniprot");
722     features.add(sf1);
723     features.add(sf2);
724     testee.addFeatureDetails(features, seq, 10);
725     menu = findMenu(testee, menuText);
726     assertNotNull(menu);
727     assertEquals(2, menu.getItemCount());
728     JMenuItem item = menu.getItem(0);
729     assertEquals("<html>helix 2-6 curly</html>", item.getText());
730     item = menu.getItem(1);
731     assertEquals("<html>chain 1 straight (uniprot)</html>", item.getText());
732
733     /*
734      * long feature descriptions are truncated to 40 characters
735      */
736     sf1.setDescription(
737             "this is a quite extraordinarily long description");
738     testee.remove(menu); // don't create the sub-menu twice
739     testee.addFeatureDetails(features, seq, 10);
740     menu = findMenu(testee, menuText);
741     item = menu.getItem(0);
742     assertEquals(
743             "<html>helix 2-6 this is a quite extraordinarily long des...</html>",
744             item.getText());
745   }
746
747   /**
748    * Returns the first component which is a JMenu with the given text
749    * 
750    * @param c
751    * @param text
752    * @return
753    */
754   private JMenu findMenu(Container c, String text)
755   {
756     for (int i = 0; i < c.getComponentCount(); i++)
757     {
758       Component comp = c.getComponent(i);
759       if ((comp instanceof JMenu) && ((JMenu) comp).getText().equals(text))
760       {
761         return (JMenu) comp;
762       }
763     }
764     return null;
765   }
766
767   @Test(groups = { "Functional" })
768   public void testAddFeatureDetails_linkedFeatures()
769   {
770     // todo tests that verify menu items for complement features
771   }
772 }