Repository: psychopy/psychopy

Issue:
Setting "Custom code" in StaticComponent doesn't seem to have any effect
The generated script doesn't contain any traces of the `Custom code` entered in the `StaticComponent`'s properties dialog.

`psychopy:master`


Discussion:

Resolving patch:
diff --git a/psychopy/experiment/components/static/__init__.py b/psychopy/experiment/components/static/__init__.py
--- a/psychopy/experiment/components/static/__init__.py
+++ b/psychopy/experiment/components/static/__init__.py
@@ -163,4 +163,11 @@ def writeParamUpdates(self, buff, updateType=None, paramNames=None):
                                       updateType=prms[fieldName].updates,
                                       params=prms)
             code = "# component updates done\n"
-            buff.writeIndented(code)
+
+            # Write custom code
+            if self.params['code']:
+                code += ("# Adding custom code for {name}\n"
+                         "{code}\n".format(name=self.params['name'],
+                                           code=self.params['code']))
+
+            buff.writeIndentedLines(code)


Test patch:
diff --git a/psychopy/tests/data/test_static_component_script.psyexp b/psychopy/tests/data/test_static_component_script.psyexp
new file mode 100644
--- /dev/null
+++ b/psychopy/tests/data/test_static_component_script.psyexp
@@ -0,0 +1,76 @@
+﻿<?xml version="1.0" ?>
+<PsychoPy2experiment encoding="utf-8" version="3.0.5">
+  <Settings>
+    <Param name="Completed URL" updates="None" val="" valType="str"/>
+    <Param name="Data filename" updates="None" val="u'data/%s_%s_%s' % (expInfo['participant'], expName, expInfo['date'])" valType="code"/>
+    <Param name="Enable Escape" updates="None" val="True" valType="bool"/>
+    <Param name="Experiment info" updates="None" val="{'participant':'', 'session':'001'}" valType="code"/>
+    <Param name="Force stereo" updates="None" val="True" valType="bool"/>
+    <Param name="Full-screen window" updates="None" val="True" valType="bool"/>
+    <Param name="HTML path" updates="None" val="html" valType="str"/>
+    <Param name="Incomplete URL" updates="None" val="" valType="str"/>
+    <Param name="JS libs" updates="None" val="packaged" valType="str"/>
+    <Param name="Monitor" updates="None" val="testMonitor" valType="str"/>
+    <Param name="Save csv file" updates="None" val="False" valType="bool"/>
+    <Param name="Save excel file" updates="None" val="False" valType="bool"/>
+    <Param name="Save log file" updates="None" val="True" valType="bool"/>
+    <Param name="Save psydat file" updates="None" val="True" valType="bool"/>
+    <Param name="Save wide csv file" updates="None" val="True" valType="bool"/>
+    <Param name="Screen" updates="None" val="1" valType="num"/>
+    <Param name="Show info dlg" updates="None" val="True" valType="bool"/>
+    <Param name="Show mouse" updates="None" val="False" valType="bool"/>
+    <Param name="Units" updates="None" val="height" valType="str"/>
+    <Param name="Use version" updates="None" val="" valType="str"/>
+    <Param name="Window size (pixels)" updates="None" val="(1024, 768)" valType="code"/>
+    <Param name="blendMode" updates="None" val="avg" valType="str"/>
+    <Param name="color" updates="None" val="$[0,0,0]" valType="str"/>
+    <Param name="colorSpace" updates="None" val="rgb" valType="str"/>
+    <Param name="expName" updates="None" val="test_static_component_script" valType="str"/>
+    <Param name="exportHTML" updates="None" val="on Sync" valType="str"/>
+    <Param name="logging level" updates="None" val="exp" valType="code"/>
+  </Settings>
+  <Routines>
+    <Routine name="trial">
+      <TextComponent name="text">
+        <Param name="color" updates="set during: trial.ISI" val="white" valType="str"/>
+        <Param name="colorSpace" updates="constant" val="rgb" valType="str"/>
+        <Param name="disabled" updates="None" val="False" valType="bool"/>
+        <Param name="durationEstim" updates="None" val="" valType="code"/>
+        <Param name="flip" updates="constant" val="" valType="str"/>
+        <Param name="font" updates="constant" val="Arial" valType="str"/>
+        <Param name="languageStyle" updates="None" val="LTR" valType="str"/>
+        <Param name="letterHeight" updates="constant" val="0.1" valType="code"/>
+        <Param name="name" updates="None" val="text" valType="code"/>
+        <Param name="opacity" updates="constant" val="1" valType="code"/>
+        <Param name="ori" updates="constant" val="0" valType="code"/>
+        <Param name="pos" updates="constant" val="(0, 0)" valType="code"/>
+        <Param name="saveStartStop" updates="None" val="True" valType="bool"/>
+        <Param name="startEstim" updates="None" val="" valType="code"/>
+        <Param name="startType" updates="None" val="time (s)" valType="str"/>
+        <Param name="startVal" updates="None" val="0.0" valType="code"/>
+        <Param name="stopType" updates="None" val="duration (s)" valType="str"/>
+        <Param name="stopVal" updates="constant" val="1.0" valType="code"/>
+        <Param name="syncScreenRefresh" updates="None" val="True" valType="bool"/>
+        <Param name="text" updates="constant" val="Any text&amp;#10;&amp;#10;including line breaks" valType="str"/>
+        <Param name="units" updates="None" val="from exp settings" valType="str"/>
+        <Param name="wrapWidth" updates="constant" val="" valType="code"/>
+      </TextComponent>
+      <StaticComponent name="ISI">
+        <Param name="code" updates="None" val="staticCode = True" valType="code"/>
+        <Param name="disabled" updates="None" val="False" valType="bool"/>
+        <Param name="durationEstim" updates="None" val="" valType="code"/>
+        <Param name="name" updates="None" val="ISI" valType="code"/>
+        <Param name="saveStartStop" updates="None" val="True" valType="bool"/>
+        <Param name="startEstim" updates="None" val="" valType="code"/>
+        <Param name="startType" updates="None" val="time (s)" valType="str"/>
+        <Param name="startVal" updates="None" val="0.0" valType="code"/>
+        <Param name="stopType" updates="None" val="duration (s)" valType="str"/>
+        <Param name="stopVal" updates="constant" val="0.5" valType="code"/>
+        <Param name="syncScreenRefresh" updates="None" val="False" valType="bool"/>
+      </StaticComponent>
+    </Routine>
+  </Routines>
+  <Flow>
+    <Routine name="trial"/>
+  </Flow>
+</PsychoPy2experiment>
diff --git a/psychopy/tests/test_scripts/test_static_component_compile.py b/psychopy/tests/test_scripts/test_static_component_compile.py
new file mode 100644
--- /dev/null
+++ b/psychopy/tests/test_scripts/test_static_component_compile.py
@@ -0,0 +1,65 @@
+import os.path
+import io
+import shutil
+from tempfile import mkdtemp
+from psychopy.scripts import psyexpCompile
+from psychopy.tests.utils import TESTS_DATA_PATH
+from psychopy.experiment import Experiment
+from psychopy.experiment.exports import IndentingBuffer
+
+class TestStaticComponentCompiled(object):
+    """
+    Test to check that static component Python code written to buffer object and output script
+    """
+    def setup(self):
+        self.temp_dir = mkdtemp()
+        self.psyexp = os.path.join(TESTS_DATA_PATH, 'test_static_component_script.psyexp')
+        self.exp = Experiment()  # Exp object for writing to buffer object
+        self.outfile = 'outfile.py'
+        self.output = ''
+        self.script = IndentingBuffer(u'')  # buffer object
+
+        # Create script
+        psyexpCompile.compileScript(infile=self.psyexp, outfile=self.outfile)
+        with io.open(self.outfile, mode='r', encoding='utf-8-sig') as f:
+            self.output = f.read()
+
+    def teardown(self):
+        shutil.rmtree(self.temp_dir)
+        self.script.close()
+
+    def test_static_is_written_to_buffer(self):
+        """Test whether code written to buffer object"""
+        # Check buffer is writable
+        assert self.script.writable()
+
+        # Load psyexp
+        self.exp.loadFromXML(filename=self.psyexp)
+        for routine in self.exp.routines:
+            for component in self.exp.routines[routine]:
+                if component.getType() == "StaticComponent":
+                    # Check component name
+                    assert component.params['name'] == 'ISI'
+                    # Check init code
+                    component.writeInitCode(self.script)
+                    assert "StaticPeriod" in self.script.getvalue()
+                    # Check start code
+                    component.writeStartTestCode(self.script)
+                    assert "ISI.start(0.5)" in self.script.getvalue()
+                    # Check custom code
+                    component.writeParamUpdates(self.script)
+                    assert "staticCode = True" in self.script.getvalue()
+                    # Check stop code
+                    component.writeStopTestCode(self.script)
+                    assert "ISI.complete()" in self.script.getvalue()
+
+    def test_static_is_written_to_script(self):
+        """Test whether code written to output file"""
+        # Check init code
+        assert "StaticPeriod" in self.output
+        # Check start code
+        assert "ISI.start(0.5)" in self.output
+        # Check custom code
+        assert "staticCode = True" in self.output
+        # Check stop code
+        assert "ISI.complete()" in self.output