JAL-3518 more pull up / test coverage of structure command generation
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 3 Mar 2020 14:24:23 +0000 (14:24 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 3 Mar 2020 14:24:23 +0000 (14:24 +0000)
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/ext/jmol/JmolCommands.java
src/jalview/ext/rbvi/chimera/ChimeraCommands.java
src/jalview/ext/rbvi/chimera/ChimeraXCommands.java
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/structure/StructureCommandsBase.java
src/jalview/structures/models/AAStructureBindingModel.java
test/jalview/ext/jmol/JmolCommandsTest.java
test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java
test/jalview/ext/rbvi/chimera/ChimeraXCommandsTest.java
test/jalview/structures/models/AAStructureBindingModelTest.java

index 6b0a696..20952f2 100644 (file)
@@ -177,6 +177,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   public String superposeStructures(AlignmentI[] _alignment,
           int[] _refStructure, HiddenColumns[] _hiddenCols)
   {
+    // TODO delete method
     while (jmolViewer.isScriptExecuting())
     {
       try
index 7dd5c0b..6f682be 100644 (file)
@@ -71,7 +71,13 @@ public class JmolCommands extends StructureCommandsBase
     return 1;
   }
 
-  @Override
+  /**
+   * Returns a string representation of the given colour suitable for inclusion
+   * in Jmol commands
+   * 
+   * @param c
+   * @return
+   */
   protected String getColourString(Color c)
   {
     return c == null ? null
@@ -79,27 +85,13 @@ public class JmolCommands extends StructureCommandsBase
                     c.getBlue());
   }
 
-  /**
-   * Returns commands (one per colour key in the map) like
-   * 
-   * <pre>
-   *   select 2:A/1.1|3-27:B/1.1|9-12:A/2.1;color[173,0,82]
-   * </pre>
-   */
-  @Override
-  public String[] colourBySequence(Map<Object, AtomSpecModel> colourMap)
-  {
-    List<String> colourCommands = buildColourCommands(colourMap);
-
-    return colourCommands.toArray(new String[colourCommands.size()]);
-  }
 
   public String[] colourBySequence(StructureSelectionManager ssm,
           String[] files,
           SequenceI[][] sequence, SequenceRenderer sr,
           AlignmentViewPanel viewPanel)
   {
-    // TODO refactor to call buildColoursMap() first...
+    // TODO delete method
 
     FeatureRenderer fr = viewPanel.getFeatureRenderer();
     FeatureColourFinder finder = new FeatureColourFinder(fr);
index 14699ef..1b1dd35 100644 (file)
@@ -62,14 +62,6 @@ public class ChimeraCommands extends StructureCommandsBase
   private static final String NO_ALTLOCS = "&~@.B-Z&~@.2-9";
 
   @Override
-  public String[] colourBySequence(Map<Object, AtomSpecModel> colourMap)
-  {
-    List<String> colourCommands = buildColourCommands(colourMap);
-
-    return colourCommands.toArray(new String[colourCommands.size()]);
-  }
-
-  @Override
   public String getColourCommand(String atomSpec, Color colour)
   {
     // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/color.html
@@ -83,7 +75,6 @@ public class ChimeraCommands extends StructureCommandsBase
    * @param colour
    * @return
    */
-  @Override
   protected String getColourString(Color colour)
   {
     return ColorUtils.toTkCode(colour);
@@ -108,9 +99,7 @@ public class ChimeraCommands extends StructureCommandsBase
     Map<String, Map<Object, AtomSpecModel>> featureMap = buildFeaturesMap(
             ssm, files, seqs, viewPanel);
 
-    List<String> commands = buildSetAttributeCommands(featureMap);
-
-    return commands.toArray(new String[commands.size()]);
+    return setAttributes(featureMap);
   }
 
   /**
@@ -354,7 +343,7 @@ public class ChimeraCommands extends StructureCommandsBase
    * @param featureMap
    * @return
    */
-  protected List<String> buildSetAttributeCommands(
+  protected String[] setAttributes(
           Map<String, Map<Object, AtomSpecModel>> featureMap)
   {
     List<String> commands = new ArrayList<>();
@@ -379,13 +368,13 @@ public class ChimeraCommands extends StructureCommandsBase
         AtomSpecModel atomSpecModel = values.get(value);
         String featureValue = value.toString();
         featureValue = featureValue.replaceAll("\\'", "&#39;");
-        String cmd = getSetAttributeCommand(attributeName, featureValue,
+        String cmd = setAttribute(attributeName, featureValue,
                 atomSpecModel);
         commands.add(cmd);
       }
     }
 
-    return commands;
+    return commands.toArray(new String[commands.size()]);
   }
 
   /**
@@ -401,7 +390,7 @@ public class ChimeraCommands extends StructureCommandsBase
    * @param atomSpecModel
    * @return
    */
-  protected String getSetAttributeCommand(String attributeName,
+  protected String setAttribute(String attributeName,
           String attributeValue,
           AtomSpecModel atomSpecModel)
   {
index 7ece122..5eba203 100644 (file)
@@ -96,7 +96,7 @@ public class ChimeraXCommands extends ChimeraCommands
    * @return
    */
   @Override
-  protected String getSetAttributeCommand(String attributeName,
+  protected String setAttribute(String attributeName,
           String attributeValue, AtomSpecModel atomSpecModel)
   {
     StringBuilder sb = new StringBuilder(128);
@@ -165,8 +165,7 @@ public class ChimeraXCommands extends ChimeraCommands
     for (String chain : atomSpec.getChains(model))
     {
       boolean firstPositionForChain = true;
-      chain = " ".equals(chain) ? chain : chain.trim();
-      sb.append("/").append(chain).append(":");
+      sb.append("/").append(chain.trim()).append(":");
       List<int[]> rangeList = atomSpec.getRanges(model, chain);
 
       /*
index 731ffea..3656204 100644 (file)
@@ -273,6 +273,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   public String superposeStructures(AlignmentI[] _alignment,
           int[] _refStructure, HiddenColumns[] _hiddenCols)
   {
+    // TODO delete method
     StringBuilder allComs = new StringBuilder(128);
     String[] files = getStructureFiles();
 
index 921c9cd..44764db 100644 (file)
@@ -58,118 +58,6 @@ public abstract class StructureCommandsBase implements StructureCommandsI
   }
 
   /**
-   * <pre>
-   * Build a data structure which records contiguous subsequences for each colour. 
-   * From this we can easily generate the viewer command for colour by sequence.
-   * Color
-   *     Model number
-   *         Chain
-   *             list of start/end ranges
-   * Ordering is by order of addition (for colours and positions), natural ordering (for models and chains)
-   * </pre>
-   * 
-   * @param ssm
-   * @param files
-   * @param sequence
-   * @param sr
-   * @param viewPanel
-   * @return
-   */
-  protected Map<Object, AtomSpecModel> buildColoursMap(
-          StructureSelectionManager ssm, String[] files,
-          SequenceI[][] sequence, SequenceRenderer sr, AlignmentViewPanel viewPanel)
-  {
-    FeatureRenderer fr = viewPanel.getFeatureRenderer();
-    FeatureColourFinder finder = new FeatureColourFinder(fr);
-    AlignViewportI viewport = viewPanel.getAlignViewport();
-    HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
-    AlignmentI al = viewport.getAlignment();
-    Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
-    Color lastColour = null;
-  
-    for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
-    {
-      final int modelNumber = pdbfnum + getModelStartNo();
-      StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
-  
-      if (mapping == null || mapping.length < 1)
-      {
-        continue;
-      }
-  
-      int startPos = -1, lastPos = -1;
-      String lastChain = "";
-      for (int s = 0; s < sequence[pdbfnum].length; s++)
-      {
-        for (int sp, m = 0; m < mapping.length; m++)
-        {
-          final SequenceI seq = sequence[pdbfnum][s];
-          if (mapping[m].getSequence() == seq
-                  && (sp = al.findIndex(seq)) > -1)
-          {
-            SequenceI asp = al.getSequenceAt(sp);
-            for (int r = 0; r < asp.getLength(); r++)
-            {
-              // no mapping to gaps in sequence
-              if (Comparison.isGap(asp.getCharAt(r)))
-              {
-                continue;
-              }
-              int pos = mapping[m].getPDBResNum(asp.findPosition(r));
-  
-              if (pos < 1 || pos == lastPos)
-              {
-                continue;
-              }
-  
-              Color colour = sr.getResidueColour(seq, r, finder);
-  
-              /*
-               * darker colour for hidden regions
-               */
-              if (!cs.isVisible(r))
-              {
-                colour = Color.GRAY;
-              }
-  
-              final String chain = mapping[m].getChain();
-  
-              /*
-               * Just keep incrementing the end position for this colour range
-               * _unless_ colour, PDB model or chain has changed, or there is a
-               * gap in the mapped residue sequence
-               */
-              final boolean newColour = !colour.equals(lastColour);
-              final boolean nonContig = lastPos + 1 != pos;
-              final boolean newChain = !chain.equals(lastChain);
-              if (newColour || nonContig || newChain)
-              {
-                if (startPos != -1)
-                {
-                  addAtomSpecRange(colourMap, lastColour, modelNumber,
-                          startPos, lastPos, lastChain);
-                }
-                startPos = pos;
-              }
-              lastColour = colour;
-              lastPos = pos;
-              lastChain = chain;
-            }
-            // final colour range
-            if (lastColour != null)
-            {
-              addAtomSpecRange(colourMap, lastColour, modelNumber, startPos,
-                      lastPos, lastChain);
-            }
-            // break;
-          }
-        }
-      }
-    }
-    return colourMap;
-  }
-
-  /**
    * Helper method to add one contiguous range to the AtomSpec model for the given
    * value (creating the model if necessary). As used by Jalview, {@code value} is
    * <ul>
@@ -203,14 +91,6 @@ public abstract class StructureCommandsBase implements StructureCommandsI
   }
 
   /**
-   * Returns a colour formatted suitable for use in viewer command syntax
-   * 
-   * @param colour
-   * @return
-   */
-  protected abstract String getColourString(Color colour);
-
-  /**
    * Traverse the map of colours/models/chains/positions to construct a list of
    * 'color' commands (one per distinct colour used). The format of each command
    * is specific to the structure viewer.
@@ -218,8 +98,8 @@ public abstract class StructureCommandsBase implements StructureCommandsI
    * @param colourMap
    * @return
    */
-  public List<String> buildColourCommands(
-          Map<Object, AtomSpecModel> colourMap)
+  @Override
+  public String[] colourBySequence(Map<Object, AtomSpecModel> colourMap)
   {
     /*
      * This version concatenates all commands into a single String (semi-colon
@@ -241,7 +121,8 @@ public abstract class StructureCommandsBase implements StructureCommandsI
       sb.append(getColourCommand(colourData, colour));
     }
     commands.add(sb.toString());
-    return commands;
+
+    return commands.toArray(new String[commands.size()]);
   }
 
   /**
index 4dfdc2a..0c1cd50 100644 (file)
@@ -867,7 +867,7 @@ public abstract class AAStructureBindingModel
       executeCommand(commandGenerator.showBackbone(), false);
 
       /*
-       * superpose each (other) sequence to it in turn
+       * superpose each (other) structure to the reference in turn
        */
       for (int i = 0; i < structures.length; i++)
       {
@@ -1273,16 +1273,20 @@ public abstract class AAStructureBindingModel
   }
 
   /**
+   * Builds a data structure which records mapped structure residues for each
+   * colour. From this we can easily generate the viewer commands for colour by
+   * sequence. Constructs and returns a map of {@code Color} to
+   * {@code AtomSpecModel}, where the atomspec model holds
+   * 
    * <pre>
-   * Build a data structure which records residues for each colour. 
-   * From this we can easily generate the viewer command for colour by sequence.
-   * Color
-   *     Model number
-   *         Chain
-   *             Residue positions
-   * Ordering is by order of addition (for colours and positions), natural ordering (for models and chains)
+   *   Model numbers
+   *     Chains
+   *       Residue positions
    * </pre>
    * 
+   * Ordering is by order of addition (for colours), natural ordering (for
+   * models and chains)
+   * 
    * @param ssm
    * @param files
    * @param sequence
index c3ece9d..d1a9df6 100644 (file)
@@ -37,7 +37,10 @@ import jalview.structure.StructureCommandsI;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureSelectionManager;
 
+import java.awt.Color;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
 
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -53,25 +56,6 @@ public class JmolCommandsTest
   }
 
   @Test(groups = { "Functional" })
-  public void testGetColourBySequenceCommand_noFeatures()
-  {
-    SequenceI seq1 = new Sequence("seq1", "MHRSQTRALK");
-    SequenceI seq2 = new Sequence("seq2", "MRLEITQSGD");
-    AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
-    AlignFrame af = new AlignFrame(al, 800, 500);
-    SequenceRenderer sr = new SequenceRenderer(af.getViewport());
-    SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
-    String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
-    StructureSelectionManager ssm = new StructureSelectionManager();
-
-    // need some mappings!
-
-    String[] commands = new JmolCommands().colourBySequence(ssm, files,
-            seqs, sr, af.alignPanel);
-    assertEquals(commands.length, 0);
-  }
-
-  @Test(groups = { "Functional" })
   public void testGetColourBySequenceCommands_hiddenColumns()
   {
     /*
@@ -179,6 +163,30 @@ public class JmolCommandsTest
   }
 
   @Test(groups = { "Functional" })
+  public void testColourBySequence()
+  {
+    Map<Object, AtomSpecModel> map = new LinkedHashMap<>();
+    JmolCommands.addAtomSpecRange(map, Color.blue, 1, 2, 5, "A");
+    JmolCommands.addAtomSpecRange(map, Color.blue, 1, 7, 7, "B");
+    JmolCommands.addAtomSpecRange(map, Color.blue, 1, 9, 23, "A");
+    JmolCommands.addAtomSpecRange(map, Color.blue, 2, 1, 1, "A");
+    JmolCommands.addAtomSpecRange(map, Color.blue, 2, 4, 7, "B");
+    JmolCommands.addAtomSpecRange(map, Color.yellow, 2, 8, 8, "A");
+    JmolCommands.addAtomSpecRange(map, Color.yellow, 2, 3, 5, "A");
+    JmolCommands.addAtomSpecRange(map, Color.red, 1, 3, 5, "A");
+    JmolCommands.addAtomSpecRange(map, Color.red, 1, 6, 9, "A");
+
+    // Colours should appear in the Jmol command in the order in which
+    // they were added; within colour, by model, by chain, ranges in start order
+    String[] commands = new JmolCommands().colourBySequence(map);
+    assertEquals(commands.length, 1);
+    String expected = "select 2-5:A/1.1|9-23:A/1.1|7:B/1.1|1:A/2.1|4-7:B/2.1;color[0,0,255]; "
+            + "select 3-5:A/2.1|8:A/2.1;color[255,255,0]; "
+            + "select 3-9:A/1.1;color[255,0,0]";
+    assertEquals(commands[0], expected);
+  }
+
+  @Test(groups = { "Functional" })
   public void testSuperposeStructures()
   {
     StructureCommandsI testee = new JmolCommands();
@@ -198,4 +206,11 @@ public class JmolCommandsTest
             toAlignSpec, refSpec, toAlignSpec, refSpec);
     assertEquals(command, expected);
   }
+
+  @Test(groups = "Functional")
+  public void testGetModelStartNo()
+  {
+    StructureCommandsI testee = new JmolCommands();
+    assertEquals(testee.getModelStartNo(), 1);
+  }
 }
index d0e6155..5f0e84b 100644 (file)
@@ -23,23 +23,12 @@ package jalview.ext.rbvi.chimera;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
-import jalview.datamodel.Alignment;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.ColumnSelection;
-import jalview.datamodel.Sequence;
-import jalview.datamodel.SequenceI;
-import jalview.gui.AlignFrame;
-import jalview.gui.SequenceRenderer;
-import jalview.schemes.JalviewColourScheme;
 import jalview.structure.AtomSpecModel;
 import jalview.structure.StructureCommandsI;
-import jalview.structure.StructureMapping;
-import jalview.structure.StructureSelectionManager;
 
 import java.awt.Color;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Map;
 
 import org.testng.annotations.Test;
@@ -48,7 +37,7 @@ public class ChimeraCommandsTest
 {
 
   @Test(groups = { "Functional" })
-  public void testBuildColourCommands()
+  public void testColourBySequence()
   {
 
     Map<Object, AtomSpecModel> map = new LinkedHashMap<>();
@@ -64,14 +53,15 @@ public class ChimeraCommandsTest
 
     // Colours should appear in the Chimera command in the order in which
     // they were added; within colour, by model, by chain, ranges in start order
-    String command = new ChimeraCommands().buildColourCommands(map).get(0);
+    String[] commands = new ChimeraCommands().colourBySequence(map);
+    assertEquals(commands.length, 1);
     assertEquals(
-            command,
+            commands[0],
             "color #0000ff #0:2-5.A,9-23.A,7.B|#1:1.A,4-7.B; color #ffff00 #1:3-5.A,8.A; color #ff0000 #0:3-9.A");
   }
 
   @Test(groups = { "Functional" })
-  public void testBuildSetAttributeCommands()
+  public void testSetAttributes()
   {
     /*
      * make a map of { featureType, {featureValue, {residue range specification } } }
@@ -86,43 +76,44 @@ public class ChimeraCommandsTest
     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 8, 20, "A");
   
     ChimeraCommands commandGenerator = new ChimeraCommands();
-    List<String> commands = commandGenerator
-            .buildSetAttributeCommands(featuresMap);
-    assertEquals(1, commands.size());
+    String[] commands = commandGenerator.setAttributes(featuresMap);
+    assertEquals(1, commands.length);
 
     /*
      * feature name gets a jv_ namespace prefix
      * feature value is quoted in case it contains spaces
      */
-    assertEquals(commands.get(0), "setattr res jv_chain 'X' #0:8-20.A");
+    assertEquals(commands[0], "setattr res jv_chain 'X' #0:8-20.A");
 
     // add same feature value, overlapping range
     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 3, 9, "A");
     // same feature value, contiguous range
     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 21, 25, "A");
-    commands = commandGenerator.buildSetAttributeCommands(featuresMap);
-    assertEquals(1, commands.size());
-    assertEquals(commands.get(0), "setattr res jv_chain 'X' #0:3-25.A");
+    commands = commandGenerator.setAttributes(featuresMap);
+    assertEquals(1, commands.length);
+    assertEquals(commands[0], "setattr res jv_chain 'X' #0:3-25.A");
 
     // same feature value and model, different chain
     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 21, 25, "B");
     // same feature value and chain, different model
     ChimeraCommands.addAtomSpecRange(featureValues, "X", 1, 26, 30, "A");
-    commands = commandGenerator.buildSetAttributeCommands(featuresMap);
-    assertEquals(1, commands.size());
-    assertEquals(commands.get(0),
-            "setattr res jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A");
+    commands = commandGenerator.setAttributes(featuresMap);
+    assertEquals(1, commands.length);
+    String expected1 = "setattr res jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A";
+    assertEquals(commands[0],
+            expected1);
 
     // same feature, different value
     ChimeraCommands.addAtomSpecRange(featureValues, "Y", 0, 40, 50, "A");
-    commands = commandGenerator.buildSetAttributeCommands(featuresMap);
-    assertEquals(2, commands.size());
+    commands = commandGenerator.setAttributes(featuresMap);
+    assertEquals(2, commands.length);
     // commands are ordered by feature type but not by value
-    // so use contains to test for the expected command:
-    assertTrue(commands
-            .contains(
-                    "setattr res jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A"));
-    assertTrue(commands.contains("setattr res jv_chain 'Y' #0:40-50.A"));
+    // so test for the expected command in either order
+    assertTrue(
+            commands[0].equals(expected1) || commands[1].equals(expected1));
+    String expected2 = "setattr res jv_chain 'Y' #0:40-50.A";
+    assertTrue(
+            commands[0].equals(expected2) || commands[1].equals(expected2));
 
     featuresMap.clear();
     featureValues.clear();
@@ -132,10 +123,10 @@ public class ChimeraCommandsTest
             "A");
     // feature names are sanitised to change non-alphanumeric to underscore
     // feature values are sanitised to encode single quote characters
-    commands = commandGenerator.buildSetAttributeCommands(featuresMap);
-    assertTrue(commands
-            .contains(
-                    "setattr res jv_side_chain_binding_ '<html>metal <a href=\"http:a.b.c/x\"> &#39;ion!' #0:7-15.A"));
+    commands = commandGenerator.setAttributes(featuresMap);
+    String expected3 = "setattr res jv_side_chain_binding_ '<html>metal <a href=\"http:a.b.c/x\"> &#39;ion!' #0:7-15.A";
+    assertTrue(
+            commands[0].equals(expected3) || commands[1].equals(expected3));
   }
 
   /**
@@ -158,60 +149,6 @@ public class ChimeraCommandsTest
             "jv_helixColor_");
   }
 
-  @Test(groups = { "Functional" })
-  public void testColourBySequence_hiddenColumns()
-  {
-    /*
-     * load these sequences, coloured by Strand propensity,
-     * with columns 2-4 hidden
-     */
-    SequenceI seq1 = new Sequence("seq1", "MHRSQSSSGG");
-    SequenceI seq2 = new Sequence("seq2", "MVRSNGGSSS");
-    AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
-    AlignFrame af = new AlignFrame(al, 800, 500);
-    af.changeColour_actionPerformed(JalviewColourScheme.Strand.toString());
-    ColumnSelection cs = new ColumnSelection();
-    cs.addElement(2);
-    cs.addElement(3);
-    cs.addElement(4);
-    af.getViewport().setColumnSelection(cs);
-    af.hideSelColumns_actionPerformed(null);
-    SequenceRenderer sr = new SequenceRenderer(af.getViewport());
-    SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
-    String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
-    StructureSelectionManager ssm = new StructureSelectionManager();
-
-    /*
-     * map residues 1-10 to residues 21-30 (atoms 105-150) in structures
-     */
-    HashMap<Integer, int[]> map = new HashMap<>();
-    for (int pos = 1; pos <= seq1.getLength(); pos++)
-    {
-      map.put(pos, new int[] { 20 + pos, 5 * (20 + pos) });
-    }
-    StructureMapping sm1 = new StructureMapping(seq1, "seq1.pdb", "pdb1",
-            "A", map, null);
-    ssm.addStructureMapping(sm1);
-    StructureMapping sm2 = new StructureMapping(seq2, "seq2.pdb", "pdb2",
-            "B", map, null);
-    ssm.addStructureMapping(sm2);
-
-    String[] commands = new ChimeraCommands()
-            .colourBySequence(ssm, files, seqs, sr, af.alignPanel);
-    assertEquals(1, commands.length);
-    String theCommand = commands[0];
-    // M colour is #82827d (see strand.html help page)
-    assertTrue(theCommand.contains("color #82827d #0:21.A|#1:21.B"));
-    // H colour is #60609f
-    assertTrue(theCommand.contains("color #60609f #0:22.A"));
-    // V colour is #ffff00
-    assertTrue(theCommand.contains("color #ffff00 #1:22.B"));
-    // hidden columns are Gray (128, 128, 128)
-    assertTrue(theCommand.contains("color #808080 #0:23-25.A|#1:23-25.B"));
-    // S and G are both coloured #4949b6
-    assertTrue(theCommand.contains("color #4949b6 #0:26-30.A|#1:26-30.B"));
-  }
-
   @Test(groups = "Functional")
   public void testGetAtomSpec()
   {
@@ -248,6 +185,27 @@ public class ChimeraCommandsTest
 
   }
 
+  @Test(groups = { "Functional" })
+  public void testSuperposeStructures()
+  {
+    StructureCommandsI testee = new ChimeraCommands();
+    AtomSpecModel ref = new AtomSpecModel();
+    ref.addRange(1, 12, 14, "A");
+    ref.addRange(1, 18, 18, "B");
+    ref.addRange(1, 22, 23, "B");
+    AtomSpecModel toAlign = new AtomSpecModel();
+    toAlign.addRange(2, 15, 17, "B");
+    toAlign.addRange(2, 20, 21, "B");
+    toAlign.addRange(2, 22, 22, "C");
+    String command = testee.superposeStructures(ref, toAlign);
+    String refSpec = "#1:12-14.A,18.B,22-23.B@CA|P&~@.B-Z&~@.2-9";
+    String toAlignSpec = "#2:15-17.B,20-21.B,22.C@CA|P&~@.B-Z&~@.2-9";
+    String expected = String.format(
+            "match %s %s;~display all; chain @CA|P; ribbon %s|%s; focus",
+            refSpec, toAlignSpec, refSpec, toAlignSpec);
+    assertEquals(command, expected);
+  }
+
   @Test(groups = "Functional")
   public void testGetAtomSpec_alphaOnly()
   {
@@ -255,54 +213,90 @@ public class ChimeraCommandsTest
     AtomSpecModel model = new AtomSpecModel();
     assertEquals(testee.getAtomSpec(model, true), "");
     model.addRange(1, 2, 4, "A");
-    assertEquals(testee.getAtomSpec(model, true), "#1:2-4.A@CA|P");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#1:2-4.A@CA|P&~@.B-Z&~@.2-9");
     model.addRange(1, 8, 8, "A");
-    assertEquals(testee.getAtomSpec(model, true), "#1:2-4.A,8.A@CA|P");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#1:2-4.A,8.A@CA|P&~@.B-Z&~@.2-9");
     model.addRange(1, 5, 7, "B");
     assertEquals(testee.getAtomSpec(model, true),
-            "#1:2-4.A,8.A,5-7.B@CA|P");
+            "#1:2-4.A,8.A,5-7.B@CA|P&~@.B-Z&~@.2-9");
     model.addRange(1, 3, 5, "A");
     assertEquals(testee.getAtomSpec(model, true),
-            "#1:2-5.A,8.A,5-7.B@CA|P");
+            "#1:2-5.A,8.A,5-7.B@CA|P&~@.B-Z&~@.2-9");
     model.addRange(0, 1, 4, "B");
     assertEquals(testee.getAtomSpec(model, true),
-            "#0:1-4.B@CA|P|#1:2-5.A,8.A,5-7.B@CA|P");
+            "#0:1-4.B@CA|P&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-7.B@CA|P&~@.B-Z&~@.2-9");
     model.addRange(0, 5, 9, "C");
     assertEquals(testee.getAtomSpec(model, true),
-            "#0:1-4.B,5-9.C@CA|P|#1:2-5.A,8.A,5-7.B@CA|P");
+            "#0:1-4.B,5-9.C@CA|P&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-7.B@CA|P&~@.B-Z&~@.2-9");
     model.addRange(1, 8, 10, "B");
     assertEquals(testee.getAtomSpec(model, true),
-            "#0:1-4.B,5-9.C@CA|P|#1:2-5.A,8.A,5-10.B@CA|P");
+            "#0:1-4.B,5-9.C@CA|P&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-10.B@CA|P&~@.B-Z&~@.2-9");
     model.addRange(1, 8, 9, "B");
     assertEquals(testee.getAtomSpec(model, true),
-            "#0:1-4.B,5-9.C@CA|P|#1:2-5.A,8.A,5-10.B@CA|P");
+            "#0:1-4.B,5-9.C@CA|P&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-10.B@CA|P&~@.B-Z&~@.2-9");
     model.addRange(0, 3, 10, "C"); // subsumes 5-9
     assertEquals(testee.getAtomSpec(model, true),
-            "#0:1-4.B,3-10.C@CA|P|#1:2-5.A,8.A,5-10.B@CA|P");
+            "#0:1-4.B,3-10.C@CA|P&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-10.B@CA|P&~@.B-Z&~@.2-9");
     model.addRange(5, 25, 35, " "); // empty chain code
     assertEquals(testee.getAtomSpec(model, true),
-            "#0:1-4.B,3-10.C@CA|P|#1:2-5.A,8.A,5-10.B@CA|P|#5:25-35.@CA|P");
+            "#0:1-4.B,3-10.C@CA|P&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-10.B@CA|P&~@.B-Z&~@.2-9|#5:25-35.@CA|P&~@.B-Z&~@.2-9");
   
   }
 
-  @Test(groups = { "Functional" })
-  public void testSuperposeStructures()
+  @Test(groups = "Functional")
+  public void testGetModelStartNo()
   {
     StructureCommandsI testee = new ChimeraCommands();
-    AtomSpecModel ref = new AtomSpecModel();
-    ref.addRange(1, 12, 14, "A");
-    ref.addRange(1, 18, 18, "B");
-    ref.addRange(1, 22, 23, "B");
-    AtomSpecModel toAlign = new AtomSpecModel();
-    toAlign.addRange(2, 15, 17, "B");
-    toAlign.addRange(2, 20, 21, "B");
-    toAlign.addRange(2, 22, 22, "C");
-    String command = testee.superposeStructures(ref, toAlign);
-    String refSpec = "#1:12-14.A,18.B,22-23.B@CA|P&~@.B-Z&~@.2-9";
-    String toAlignSpec = "#2:15-17.B,20-21.B,22.C@CA|P&~@.B-Z&~@.2-9";
-    String expected = String.format(
-            "match %s %s;~display all; chain @CA|P; ribbon %s|%s; focus",
-            refSpec, toAlignSpec, refSpec, toAlignSpec);
-    assertEquals(command, expected);
+    assertEquals(testee.getModelStartNo(), 0);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetResidueSpec()
+  {
+    ChimeraCommands testee = new ChimeraCommands();
+    assertEquals(testee.getResidueSpec("ALA"), "::ALA");
+  }
+
+  @Test(groups = "Functional")
+  public void testShowBackbone()
+  {
+    ChimeraCommands testee = new ChimeraCommands();
+    assertEquals(testee.showBackbone(), "~display all;chain @CA|P");
+  }
+
+  @Test(groups = "Functional")
+  public void testOpenCommandFile()
+  {
+    ChimeraCommands testee = new ChimeraCommands();
+    assertEquals(testee.openCommandFile("nowhere"), "open cmd:nowhere");
+  }
+
+  @Test(groups = "Functional")
+  public void testSaveSession()
+  {
+    ChimeraCommands testee = new ChimeraCommands();
+    assertEquals(testee.saveSession("somewhere"), "save somewhere");
+  }
+
+  @Test(groups = "Functional")
+  public void testGetColourCommand()
+  {
+    ChimeraCommands testee = new ChimeraCommands();
+    assertEquals(testee.getColourCommand("something", Color.MAGENTA),
+            "color #ff00ff something");
+  }
+
+  @Test(groups = "Functional")
+  public void testSetAttribute()
+  {
+    ChimeraCommands testee = new ChimeraCommands();
+    AtomSpecModel model = new AtomSpecModel();
+    model.addRange(1, 89, 92, "A");
+    model.addRange(2, 12, 20, "B");
+    model.addRange(2, 8, 9, "B");
+    assertEquals(testee.setAttribute("phi", "27.3", model),
+            "setattr res phi '27.3' #1:89-92.A|#2:8-9.B,12-20.B");
   }
 }
index a9eaca3..ddda2c0 100644 (file)
@@ -23,55 +23,65 @@ package jalview.ext.rbvi.chimera;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
-import jalview.datamodel.Alignment;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.ColumnSelection;
-import jalview.datamodel.Sequence;
-import jalview.datamodel.SequenceI;
-import jalview.gui.AlignFrame;
-import jalview.gui.SequenceRenderer;
-import jalview.schemes.JalviewColourScheme;
 import jalview.structure.AtomSpecModel;
 import jalview.structure.StructureCommandsI;
-import jalview.structure.StructureMapping;
-import jalview.structure.StructureSelectionManager;
 
 import java.awt.Color;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Map;
 
 import org.testng.annotations.Test;
 
 public class ChimeraXCommandsTest
 {
+  @Test(groups = { "Functional" })
+  public void testColourByCharge()
+  {
+    String cmd = new ChimeraXCommands().colourByCharge();
+    assertEquals(cmd,
+            "color white;color :ASP,GLU red;color :LYS,ARG blue;color :CYS yellow");
+  }
 
   @Test(groups = { "Functional" })
-  public void testBuildColourCommands()
+  public void testColourByChain()
   {
+    String cmd = new ChimeraXCommands().colourByChain();
+    assertEquals(cmd, "rainbow chain");
+  }
 
+  @Test(groups = { "Functional" })
+  public void testFocusView()
+  {
+    String cmd = new ChimeraXCommands().focusView();
+    assertEquals(cmd, "view");
+  }
+
+  @Test(groups = { "Functional" })
+  public void testColourBySequence()
+  {
     Map<Object, AtomSpecModel> map = new LinkedHashMap<>();
-    ChimeraCommands.addAtomSpecRange(map, Color.blue, 0, 2, 5, "A");
-    ChimeraCommands.addAtomSpecRange(map, Color.blue, 0, 7, 7, "B");
-    ChimeraCommands.addAtomSpecRange(map, Color.blue, 0, 9, 23, "A");
-    ChimeraCommands.addAtomSpecRange(map, Color.blue, 1, 1, 1, "A");
-    ChimeraCommands.addAtomSpecRange(map, Color.blue, 1, 4, 7, "B");
-    ChimeraCommands.addAtomSpecRange(map, Color.yellow, 1, 8, 8, "A");
-    ChimeraCommands.addAtomSpecRange(map, Color.yellow, 1, 3, 5, "A");
-    ChimeraCommands.addAtomSpecRange(map, Color.red, 0, 3, 5, "A");
-    ChimeraCommands.addAtomSpecRange(map, Color.red, 0, 6, 9, "A");
+    ChimeraCommands.addAtomSpecRange(map, Color.blue, 1, 2, 5, "A");
+    ChimeraCommands.addAtomSpecRange(map, Color.blue, 1, 7, 7, "B");
+    ChimeraCommands.addAtomSpecRange(map, Color.blue, 1, 9, 23, "A");
+    ChimeraCommands.addAtomSpecRange(map, Color.blue, 2, 1, 1, "A");
+    ChimeraCommands.addAtomSpecRange(map, Color.blue, 2, 4, 7, "B");
+    ChimeraCommands.addAtomSpecRange(map, Color.yellow, 2, 8, 8, "A");
+    ChimeraCommands.addAtomSpecRange(map, Color.yellow, 2, 3, 5, "A");
+    ChimeraCommands.addAtomSpecRange(map, Color.red, 1, 3, 5, "A");
+    ChimeraCommands.addAtomSpecRange(map, Color.red, 1, 6, 9, "A");
 
     // Colours should appear in the Chimera command in the order in which
     // they were added; within colour, by model, by chain, ranges in start order
-    String command = new ChimeraXCommands().buildColourCommands(map).get(0);
+    String[] commands = new ChimeraXCommands().colourBySequence(map);
+    assertEquals(commands.length, 1);
     assertEquals(
-            command,
-            "color #0/A:2-5,9-23/B:7|#1/A:1/B:4-7 #0000ff; color #1/A:3-5,8 #ffff00; color #0/A:3-9 #ff0000");
+            commands[0],
+            "color #1/A:2-5,9-23/B:7|#2/A:1/B:4-7 #0000ff; color #2/A:3-5,8 #ffff00; color #1/A:3-9 #ff0000");
   }
 
   @Test(groups = { "Functional" })
-  public void testBuildSetAttributeCommands()
+  public void testSetAttributes()
   {
     /*
      * make a map of { featureType, {featureValue, {residue range specification } } }
@@ -86,46 +96,45 @@ public class ChimeraXCommandsTest
     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 8, 20, "A");
   
     ChimeraXCommands commandGenerator = new ChimeraXCommands();
-    List<String> commands = commandGenerator
-            .buildSetAttributeCommands(featuresMap);
-    assertEquals(1, commands.size());
+    String[] commands = commandGenerator.setAttributes(featuresMap);
+    assertEquals(commands.length, 1);
 
     /*
      * feature name gets a jv_ namespace prefix
      * feature value is quoted in case it contains spaces
      */
-    assertEquals(commands.get(0),
+    assertEquals(commands[0],
             "setattr #0/A:8-20 res jv_chain 'X' create true");
 
     // add same feature value, overlapping range
     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 3, 9, "A");
     // same feature value, contiguous range
     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 21, 25, "A");
-    commands = commandGenerator.buildSetAttributeCommands(featuresMap);
-    assertEquals(1, commands.size());
-    assertEquals(commands.get(0),
+    commands = commandGenerator.setAttributes(featuresMap);
+    assertEquals(commands.length, 1);
+    assertEquals(commands[0],
             "setattr #0/A:3-25 res jv_chain 'X' create true");
 
     // same feature value and model, different chain
     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 21, 25, "B");
     // same feature value and chain, different model
     ChimeraCommands.addAtomSpecRange(featureValues, "X", 1, 26, 30, "A");
-    commands = commandGenerator.buildSetAttributeCommands(featuresMap);
-    assertEquals(1, commands.size());
-    assertEquals(commands.get(0),
-            "setattr #0/A:3-25/B:21-25|#1/A:26-30 res jv_chain 'X' create true");
+    commands = commandGenerator.setAttributes(featuresMap);
+    assertEquals(commands.length, 1);
+    String expected1 = "setattr #0/A:3-25/B:21-25|#1/A:26-30 res jv_chain 'X' create true";
+    assertEquals(commands[0], expected1);
 
     // same feature, different value
     ChimeraCommands.addAtomSpecRange(featureValues, "Y", 0, 40, 50, "A");
-    commands = commandGenerator.buildSetAttributeCommands(featuresMap);
-    assertEquals(2, commands.size());
+    commands = commandGenerator.setAttributes(featuresMap);
+    assertEquals(2, commands.length);
     // commands are ordered by feature type but not by value
-    // so use contains to test for the expected command:
-    assertTrue(commands
-            .contains(
-                    "setattr #0/A:3-25/B:21-25|#1/A:26-30 res jv_chain 'X' create true"));
-    assertTrue(commands
-            .contains("setattr #0/A:40-50 res jv_chain 'Y' create true"));
+    // so test for the expected command in either order
+    assertTrue(
+            commands[0].equals(expected1) || commands[1].equals(expected1));
+    String expected2 = "setattr #0/A:40-50 res jv_chain 'Y' create true";
+    assertTrue(
+            commands[0].equals(expected2) || commands[1].equals(expected2));
 
     featuresMap.clear();
     featureValues.clear();
@@ -135,63 +144,10 @@ public class ChimeraXCommandsTest
             "A");
     // feature names are sanitised to change non-alphanumeric to underscore
     // feature values are sanitised to encode single quote characters
-    commands = commandGenerator.buildSetAttributeCommands(featuresMap);
-    assertTrue(commands.contains(
-            "setattr #0/A:7-15 res jv_side_chain_binding_ '<html>metal <a href=\"http:a.b.c/x\"> &#39;ion!' create true"));
-  }
-
-  @Test(groups = { "Functional" })
-  public void testColourBySequence_hiddenColumns()
-  {
-    /*
-     * load these sequences, coloured by Strand propensity,
-     * with columns 2-4 hidden
-     */
-    SequenceI seq1 = new Sequence("seq1", "MHRSQSSSGG");
-    SequenceI seq2 = new Sequence("seq2", "MVRSNGGSSS");
-    AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
-    AlignFrame af = new AlignFrame(al, 800, 500);
-    af.changeColour_actionPerformed(JalviewColourScheme.Strand.toString());
-    ColumnSelection cs = new ColumnSelection();
-    cs.addElement(2);
-    cs.addElement(3);
-    cs.addElement(4);
-    af.getViewport().setColumnSelection(cs);
-    af.hideSelColumns_actionPerformed(null);
-    SequenceRenderer sr = new SequenceRenderer(af.getViewport());
-    SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
-    String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
-    StructureSelectionManager ssm = new StructureSelectionManager();
-
-    /*
-     * map residues 1-10 to residues 21-30 (atoms 105-150) in structures
-     */
-    HashMap<Integer, int[]> map = new HashMap<>();
-    for (int pos = 1; pos <= seq1.getLength(); pos++)
-    {
-      map.put(pos, new int[] { 20 + pos, 5 * (20 + pos) });
-    }
-    StructureMapping sm1 = new StructureMapping(seq1, "seq1.pdb", "pdb1",
-            "A", map, null);
-    ssm.addStructureMapping(sm1);
-    StructureMapping sm2 = new StructureMapping(seq2, "seq2.pdb", "pdb2",
-            "B", map, null);
-    ssm.addStructureMapping(sm2);
-
-    String[] commands = new ChimeraXCommands()
-            .colourBySequence(ssm, files, seqs, sr, af.alignPanel);
-    assertEquals(1, commands.length);
-    String theCommand = commands[0];
-    // M colour is #82827d (see strand.html help page)
-    assertTrue(theCommand.contains("color #0/A:21|#1/B:21 #82827d"));// #0:21.A|#1:21.B"));
-    // H colour is #60609f
-    assertTrue(theCommand.contains("color #0/A:22 #60609f"));
-    // V colour is #ffff00
-    assertTrue(theCommand.contains("color #1/B:22 #ffff00"));
-    // hidden columns are Gray (128, 128, 128)
-    assertTrue(theCommand.contains("color #0/A:23-25|#1/B:23-25"));
-    // S and G are both coloured #4949b6
-    assertTrue(theCommand.contains("color #0/A:26-30|#1/B:26-30"));
+    commands = commandGenerator.setAttributes(featuresMap);
+    String expected3 = "setattr #0/A:7-15 res jv_side_chain_binding_ '<html>metal <a href=\"http:a.b.c/x\"> &#39;ion!' create true";
+    assertTrue(
+            commands[0].equals(expected3) || commands[1].equals(expected3));
   }
 
   @Test(groups = { "Functional" })
@@ -209,9 +165,137 @@ public class ChimeraXCommandsTest
     String command = testee.superposeStructures(ref, toAlign);
     String refSpec = "#1/A:12-14/B:18,22-23";
     String toAlignSpec = "#2/B:15-17,20-21/C:22";
+
+    /*
+     * superposition arguments include AlphaCarbon restriction,
+     * ribbon command does not
+     */
     String expected = String.format(
-            "align %s %s;~display all; chain @CA|P; ribbon %s|%s; focus",
+            "align %s@CA|P toAtoms %s@CA|P; ribbon %s|%s; view",
             refSpec, toAlignSpec, refSpec, toAlignSpec);
     assertEquals(command, expected);
   }
+
+  @Test(groups = "Functional")
+  public void testGetAtomSpec()
+  {
+    StructureCommandsI testee = new ChimeraXCommands();
+    AtomSpecModel model = new AtomSpecModel();
+    assertEquals(testee.getAtomSpec(model, false), "");
+    model.addRange(1, 2, 4, "A");
+    assertEquals(testee.getAtomSpec(model, false), "#1/A:2-4");
+    model.addRange(1, 8, 8, "A");
+    assertEquals(testee.getAtomSpec(model, false), "#1/A:2-4,8");
+    model.addRange(1, 5, 7, "B");
+    assertEquals(testee.getAtomSpec(model, false), "#1/A:2-4,8/B:5-7");
+    model.addRange(1, 3, 5, "A");
+    assertEquals(testee.getAtomSpec(model, false), "#1/A:2-5,8/B:5-7");
+    model.addRange(0, 1, 4, "B");
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0/B:1-4|#1/A:2-5,8/B:5-7");
+    model.addRange(0, 5, 9, "C");
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0/B:1-4/C:5-9|#1/A:2-5,8/B:5-7");
+    model.addRange(1, 8, 10, "B");
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0/B:1-4/C:5-9|#1/A:2-5,8/B:5-10");
+    model.addRange(1, 8, 9, "B");
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0/B:1-4/C:5-9|#1/A:2-5,8/B:5-10");
+    model.addRange(0, 3, 10, "C"); // subsumes 5-9
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0/B:1-4/C:3-10|#1/A:2-5,8/B:5-10");
+    model.addRange(5, 25, 35, " ");
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0/B:1-4/C:3-10|#1/A:2-5,8/B:5-10|#5/:25-35");
+  }
+
+  @Test(groups = "Functional")
+  public void testGetAtomSpec_alphaOnly()
+  {
+    StructureCommandsI testee = new ChimeraXCommands();
+    AtomSpecModel model = new AtomSpecModel();
+    assertEquals(testee.getAtomSpec(model, true), "");
+    model.addRange(1, 2, 4, "A");
+    assertEquals(testee.getAtomSpec(model, true), "#1/A:2-4@CA|P");
+    model.addRange(1, 8, 8, "A");
+    assertEquals(testee.getAtomSpec(model, true), "#1/A:2-4,8@CA|P");
+    model.addRange(1, 5, 7, "B");
+    assertEquals(testee.getAtomSpec(model, true), "#1/A:2-4,8/B:5-7@CA|P");
+    model.addRange(1, 3, 5, "A");
+    assertEquals(testee.getAtomSpec(model, true), "#1/A:2-5,8/B:5-7@CA|P");
+    model.addRange(0, 1, 4, "B");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0/B:1-4@CA|P|#1/A:2-5,8/B:5-7@CA|P");
+    model.addRange(0, 5, 9, "C");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0/B:1-4/C:5-9@CA|P|#1/A:2-5,8/B:5-7@CA|P");
+    model.addRange(1, 8, 10, "B");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0/B:1-4/C:5-9@CA|P|#1/A:2-5,8/B:5-10@CA|P");
+    model.addRange(1, 8, 9, "B");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0/B:1-4/C:5-9@CA|P|#1/A:2-5,8/B:5-10@CA|P");
+    model.addRange(0, 3, 10, "C"); // subsumes 5-9
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0/B:1-4/C:3-10@CA|P|#1/A:2-5,8/B:5-10@CA|P");
+    model.addRange(5, 25, 35, " "); // empty chain code
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0/B:1-4/C:3-10@CA|P|#1/A:2-5,8/B:5-10@CA|P|#5/:25-35@CA|P");
+  }
+
+  @Test(groups = "Functional")
+  public void testGetModelStartNo()
+  {
+    StructureCommandsI testee = new ChimeraXCommands();
+    assertEquals(testee.getModelStartNo(), 1);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetResidueSpec()
+  {
+    ChimeraCommands testee = new ChimeraXCommands();
+    assertEquals(testee.getResidueSpec("ALA"), ":ALA");
+  }
+
+  @Test(groups = "Functional")
+  public void testShowBackbone()
+  {
+    ChimeraCommands testee = new ChimeraXCommands();
+    assertEquals(testee.showBackbone(), "~display all;show @CA|P pbonds");
+  }
+
+  @Test(groups = "Functional")
+  public void testOpenCommandFile()
+  {
+    ChimeraCommands testee = new ChimeraXCommands();
+    assertEquals(testee.openCommandFile("nowhere"), "open nowhere");
+  }
+
+  @Test(groups = "Functional")
+  public void testSaveSession()
+  {
+    ChimeraCommands testee = new ChimeraXCommands();
+    assertEquals(testee.saveSession("somewhere"), "save session somewhere");
+  }
+
+  @Test(groups = "Functional")
+  public void testGetColourCommand()
+  {
+    ChimeraCommands testee = new ChimeraXCommands();
+    assertEquals(testee.getColourCommand("something", Color.MAGENTA),
+            "color something #ff00ff");
+  }
+
+  @Test(groups = "Functional")
+  public void testSetAttribute()
+  {
+    ChimeraCommands testee = new ChimeraXCommands();
+    AtomSpecModel model = new AtomSpecModel();
+    model.addRange(1, 89, 92, "A");
+    model.addRange(2, 12, 20, "B");
+    model.addRange(2, 8, 9, "B");
+    assertEquals(testee.setAttribute("phi", "27.3", model),
+            "setattr #1/A:89-92|#2/B:8-9,12-20 res phi '27.3' create true");
+  }
 }
index c201926..f901cbd 100644 (file)
  */
 package jalview.structures.models;
 
+import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
-import static org.testng.AssertJUnit.assertEquals;
-import static org.testng.AssertJUnit.assertTrue;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
 
 import jalview.api.AlignmentViewPanel;
 import jalview.api.SequenceRenderer;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.PDBEntry.Type;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
+import jalview.ext.rbvi.chimera.ChimeraCommands;
+import jalview.gui.AlignFrame;
 import jalview.gui.JvOptionPane;
 import jalview.gui.StructureViewer.ViewerType;
 import jalview.io.DataSourceType;
 import jalview.io.FileFormats;
+import jalview.schemes.JalviewColourScheme;
 import jalview.structure.AtomSpec;
+import jalview.structure.AtomSpecModel;
 import jalview.structure.StructureCommandsI.SuperposeData;
+import jalview.structure.StructureMapping;
 import jalview.structure.StructureSelectionManager;
 
+import java.awt.Color;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.BitSet;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import junit.extensions.PA;
+
 /**
  * Unit tests for non-abstract methods of abstract base class
  * 
@@ -181,9 +193,9 @@ public class AAStructureBindingModelTest
     String[][] chains = binder.getChains();
     assertFalse(chains == null || chains[0] == null,
             "No chains discovered by binding");
-    assertEquals(2, chains[0].length);
-    assertEquals("A", chains[0][0]);
-    assertEquals("B", chains[0][1]);
+    assertEquals(chains[0].length, 2);
+    assertEquals(chains[0][0], "A");
+    assertEquals(chains[0][1], "B");
   }
   AAStructureBindingModel testee;
 
@@ -222,7 +234,22 @@ public class AAStructureBindingModelTest
     ssm.setMapping(new SequenceI[] { seq3 }, null, PDB_3,
             DataSourceType.PASTE, null);
 
-    testee = new AAStructureBindingModel(ssm, pdbFiles, seqs, null)
+    testee = newBindingModel(pdbFiles, seqs, ssm);
+  }
+
+  /**
+   * A helper method to construct the test target object
+   * 
+   * @param pdbFiles
+   * @param seqs
+   * @param ssm
+   */
+  protected AAStructureBindingModel newBindingModel(PDBEntry[] pdbFiles,
+          SequenceI[][] seqs,
+          StructureSelectionManager ssm)
+  {
+    AAStructureBindingModel model = new AAStructureBindingModel(ssm,
+            pdbFiles, seqs, null)
     {
       @Override
       public String[] getStructureFiles()
@@ -271,6 +298,8 @@ public class AAStructureBindingModelTest
         return null;
       }
     };
+    PA.setValue(model, "commandGenerator", new ChimeraCommands());
+    return model;
   }
 
   /**
@@ -301,7 +330,7 @@ public class AAStructureBindingModelTest
     int refStructure = testee
             .findSuperposableResidues(al, matched, structs);
 
-    assertEquals(0, refStructure);
+    assertEquals(refStructure, 0);
 
     /*
      * only ungapped, structure-mapped columns are superposable
@@ -313,18 +342,19 @@ public class AAStructureBindingModelTest
     assertTrue(matched.get(4));
     assertTrue(matched.get(5)); // gap in second sequence
 
-    assertEquals("1YCS", structs[0].pdbId);
-    assertEquals("3A6S", structs[1].pdbId);
-    assertEquals("1OOT", structs[2].pdbId);
-    assertEquals("A", structs[0].chain); // ? struct has chains A _and_ B
-    assertEquals("B", structs[1].chain);
-    assertEquals("A", structs[2].chain);
+    assertEquals(structs[0].pdbId, "1YCS");
+    assertEquals(structs[1].pdbId, "3A6S");
+    assertEquals(structs[2].pdbId, "1OOT");
+    assertEquals(structs[0].chain, "A"); // ? struct has chains A _and_ B
+    assertEquals(structs[1].chain, "B");
+    assertEquals(structs[2].chain, "A");
     // the 0's for unsuperposable positions propagate down the columns:
-    assertEquals("[0, 97, 98, 99, 100, 102]",
-            Arrays.toString(structs[0].pdbResNo));
-    assertEquals("[0, 2, 0, 3, 4, 5]", Arrays.toString(structs[1].pdbResNo));
-    assertEquals("[0, 8, 0, 0, 10, 12]",
-            Arrays.toString(structs[2].pdbResNo));
+    assertEquals(Arrays.toString(structs[0].pdbResNo),
+            "[0, 97, 98, 99, 100, 102]");
+    assertEquals(Arrays.toString(structs[1].pdbResNo),
+            "[0, 2, 0, 3, 4, 5]");
+    assertEquals(Arrays.toString(structs[2].pdbResNo),
+            "[0, 8, 0, 0, 10, 12]");
   }
 
   @Test(groups = { "Functional" })
@@ -351,7 +381,7 @@ public class AAStructureBindingModelTest
     int refStructure = testee
             .findSuperposableResidues(al, matched, structs);
 
-    assertEquals(0, refStructure);
+    assertEquals(refStructure, 0);
 
     // only ungapped, structure-mapped columns are not superposable
     assertFalse(matched.get(0));
@@ -361,4 +391,100 @@ public class AAStructureBindingModelTest
     assertFalse(matched.get(4)); // superposable, but hidden, column
     assertTrue(matched.get(5));
   }
+
+  @Test(groups = { "Functional" })
+  public void testBuildColoursMap()
+  {
+    /*
+     * load these sequences, coloured by Strand propensity,
+     * with columns 2-4 hidden
+     */
+    SequenceI seq1 = new Sequence("seq1", "MHRSQSSSGG");
+    SequenceI seq2 = new Sequence("seq2", "MVRSNGGSSS");
+    AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
+    AlignFrame af = new AlignFrame(al, 800, 500);
+    af.changeColour_actionPerformed(JalviewColourScheme.Strand.toString());
+    ColumnSelection cs = new ColumnSelection();
+    cs.addElement(2);
+    cs.addElement(3);
+    cs.addElement(4);
+    af.getViewport().setColumnSelection(cs);
+    af.hideSelColumns_actionPerformed(null);
+    SequenceRenderer sr = new jalview.gui.SequenceRenderer(
+            af.getViewport());
+    SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
+    String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
+    PDBEntry[] pdbFiles = new PDBEntry[2];
+    pdbFiles[0] = new PDBEntry("PDB1", "A", Type.PDB, "INLINEPDB1");
+    pdbFiles[1] = new PDBEntry("PDB2", "B", Type.PDB, "INLINEPDB2");
+    StructureSelectionManager ssm = new StructureSelectionManager();
+  
+    /*
+     * map residues 1-10 to residues 21-30 (atoms 105-150) in structures
+     */
+    HashMap<Integer, int[]> map = new HashMap<>();
+    for (int pos = 1; pos <= seq1.getLength(); pos++)
+    {
+      map.put(pos, new int[] { 20 + pos, 5 * (20 + pos) });
+    }
+    StructureMapping sm1 = new StructureMapping(seq1, "seq1.pdb", "pdb1",
+            "A", map, null);
+    ssm.addStructureMapping(sm1);
+    StructureMapping sm2 = new StructureMapping(seq2, "seq2.pdb", "pdb2",
+            "B", map, null);
+    ssm.addStructureMapping(sm2);
+
+    AAStructureBindingModel binding = newBindingModel(pdbFiles, seqs, ssm);
+
+    /*
+     * method under test builds a map of structures residues by colour
+     * verify the map holds what it should
+     */
+    Map<Object, AtomSpecModel> colours = binding.buildColoursMap(ssm, files,
+            seqs, sr, af.alignPanel);
+    ChimeraCommands helper = new ChimeraCommands();
+    
+    /*
+     * M colour is #82827d (see strand.html help page)
+     * sequence residue 1 mapped to structure residue 21
+     */
+    Color mColor = new Color(0x82827d);
+    AtomSpecModel atomSpec = colours.get(mColor);
+    assertNotNull(atomSpec);
+    assertEquals(helper.getAtomSpec(atomSpec, false), "#0:21.A|#1:21.B");
+
+    /*
+     * H colour is #60609f, seq1.2 mapped to structure 0 residue 22
+     */
+    Color hColor = new Color(0x60609f);
+    atomSpec = colours.get(hColor);
+    assertNotNull(atomSpec);
+    assertEquals(helper.getAtomSpec(atomSpec, false), "#0:22.A");
+
+    /*
+     * V colour is #ffff00, seq2.2 mapped to structure 1 residue 22
+     */
+    Color vColor = new Color(0xffff00);
+    atomSpec = colours.get(vColor);
+    assertNotNull(atomSpec);
+    assertEquals(helper.getAtomSpec(atomSpec, false), "#1:22.B");
+
+    /*
+     * hidden columns are Gray (128, 128, 128)
+     * sequence positions 3-5 mapped to structure residues 23-25
+     */
+    Color gray = new Color(128, 128, 128);
+    atomSpec = colours.get(gray);
+    assertNotNull(atomSpec);
+    assertEquals(helper.getAtomSpec(atomSpec, false), "#0:23-25.A|#1:23-25.B");
+
+    /*
+     * S and G are both coloured #4949b6, structure residues 26-30
+     */
+    Color sgColour = new Color(0x4949b6);
+    atomSpec = colours.get(sgColour);
+    assertNotNull(atomSpec);
+    assertEquals(helper.getAtomSpec(atomSpec, false),
+            "#0:26-30.A|#1:26-30.B");
+  }
 }
\ No newline at end of file