Browse Source

[REVIEW] Zend_Dojo_BuildLayer promoted to trunk

git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@16714 44c647ce-9c0f-0410-b52a-842ac1e357ba
matthew 16 years ago
parent
commit
712c6f9107

+ 1 - 0
documentation/manual/en/manual.xml.in

@@ -169,6 +169,7 @@
         <xi:include href="module_specs/Zend_Dojo-Data.xml" />
         <xi:include href="module_specs/Zend_Dojo-View.xml" parse="xml" />
         <xi:include href="module_specs/Zend_Dojo-Form.xml" parse="xml" />
+        <xi:include href="module_specs/Zend_Dojo-BuildLayers.xml" />
     </chapter>
 
     <chapter id="zend.dom">

+ 423 - 0
documentation/manual/en/module_specs/Zend_Dojo-BuildLayers.xml

@@ -0,0 +1,423 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<sect1 id="zend.dojo.build-layers">
+    <title>Zend_Dojo build layer support</title>
+
+    <sect2 id="zend.dojo.build-layers.introduction">
+        <title>Introduction</title>
+
+        <para>
+            Dojo build layers provide a clean path from development to
+            production when using Dojo for your UI layer. In development, you
+            can have load-on-demand, rapid application prototyping; a build
+            layer takes all Dojo dependencies and compiles them to a single
+            file, optionally stripping whitespace and comments, and performing
+            code heuristics to allow further minification of variable names.
+            Additionally, it can do CSS minification.
+        </para>
+
+        <para>
+            In order to create a build layer, you would traditionally create a
+            JavaScript file that has <code>dojo.require</code> statements for
+            each dependency, and optionally some additional code that might run
+            when the script is loaded. As an example:
+        </para>
+
+        <programlisting role="javascript"><![CDATA[
+dojo.provide("custom.main");
+
+dojo.require("dijit.layout.TabContainer");
+dojo.require("dijit.layout.ContentPane");
+dojo.require("dijit.form.Form");
+dojo.require("dijit.form.Button");
+dojo.require("dijit.form.TextBox");
+]]></programlisting>
+
+        <para>
+            This script is generally referred to as a "layer" script.
+        </para>
+
+        <para>
+            Then, in your application's layout, you'd instruct Dojo to load this
+            module:
+        </para>
+
+        <programlisting role="html"><![CDATA[
+<html>
+<head>
+    <script type="text/javascript" src="/js/dojo/dojo.js"></script>
+    <script type="text/javascript">
+        dojo.registerModulePath("custom", "../custom/");
+        dojo.require("custom.main");
+    </script>
+]]></programlisting>
+
+        <para>
+            If you use <classname>Zend_Dojo</classname> to do this, you'd do the
+            following:
+        </para>
+
+        <programlisting role="php"><![CDATA[
+$view->dojo()->registerModulePath('custom', '../custom/')
+             ->requireModule('custom.main');                     
+]]></programlisting>
+
+        <para>
+            But since <classname>Zend_Dojo</classname> aggregates your various
+            <code>dojo.require</code> statements, how do you create your layer
+            script? You could open each page and view the generated
+            <code>dojo.require</code> statements, and cut and paste them into a
+            layer script file manually.
+        </para>
+
+        <para>
+            However, a better solution exists: since
+            <classname>Zend_Dojo</classname> aggregates this information
+            already, you can simply pull that information and build your layer
+            file. This is the purpose of
+            <classname>Zend_Dojo_BuildLayer</classname>.
+        </para>
+    </sect2>
+
+    <sect2 id="zend.dojo.build-layers.usage">
+        <title>Generating Custom Module Layers with Zend_Dojo_BuildLayer</title>
+
+        <para>
+            At its simplest, you simply instantiate
+            <classname>Zend_Dojo_BuildLayer</classname>, feed it the view object
+            and the name of your custom module layer, and have it generate the
+            content of the layer file; it is up to you to then write it to disk.
+        </para>
+
+        <para>
+            As an example, let's say you wanted to create the module layer
+            "custom.main". Assuming you follow the recommended project directory
+            structure, and that you are storing your JavaScript files under
+            <filename>public/js/</filename>, you could do the following:
+        </para>
+
+        <programlisting role="php"><![CDATA[
+$build = new Zend_Dojo_BuildLayer(array(
+    'view'      => $view,
+    'layerName' => 'custom.main',
+));
+
+$layerContents = $build->generateLayerScript();
+$filename      = APPLICATION_PATH . '/../public/js/custom/main.js';
+if (!dir_exists(dirname($filename))) {
+    mkdir(dirname($filename));
+}
+file_put_contents($filename, $layerContents);
+]]></programlisting>
+
+        <para>
+            When should you do the above? For it to work correctly, you need to
+            do it after all view scripts and the layout have been rendered, to
+            ensure that the <code>dojo()</code> helper is fully populated. One
+            easy way to do so is using a front controller plugin, with a
+            <code>dispatchLoopShutdown()</code> hook:
+        </para>
+
+        <programlisting role="php"><![CDATA[
+class App_Plugin_DojoLayer extends Zend_Controller_Plugin_Abstract
+{
+    public $layerScript = APPLICATION_PATH . '/../public/js/custom/main.js';
+    protected $_build;
+
+    public function dispatchLoopShutdown()
+    {
+        if (!file_exists($this->layerScript)) {
+            $this->generateDojoLayer();
+        }
+    }
+
+    public function getBuild()
+    {
+        if (null === $this->_build) {
+            $this->_build = new Zend_Dojo_BuildLayer(array(
+                'view'      => $view,
+                'layerName' => 'custom.main',
+            ));
+        }
+        return $this->_build;
+    }
+
+    public function generateDojoLayer()
+    {
+        $build = $this->getBuild();
+        $layerContents = $build->generateLayerScript();
+        if (!dir_exists(dirname($this->layerScript))) {
+            mkdir(dirname($this->layerScript));
+        }
+        file_put_contents($this->layerScript, $layerContents);
+    }
+}
+]]></programlisting>
+
+        <note>
+            <title>Do not generate the layer on every page</title>
+
+            <para>
+                It's tempting to generate the layer script on each and every
+                page. However, this is resource intensive, as it must write to
+                the disk on each page. Additionally, since the mtime of the file
+                will keep changing, you will get no benefits of client-side
+                caching. Write the file once.
+            </para>
+        </note>
+
+        <sect3 id="zend.dojo.build-layers.usage.options">
+            <title>BuildLayer options</title>
+
+            <para>
+                The above functionality will suffice for most situations. For
+                those needing more customization, a variety of options may be
+                invoked.
+            </para>
+
+            <sect4 id="zend.dojo.build-layers.usage.options.view">
+                <title>Setting the view object</title>
+
+                <para>
+                    While the view object may be passed during instantiation,
+                    you may also pass it in to an instance via the
+                    <methodname>setView()</methodname> method:
+                </para>
+
+                <programlisting role="php"><![CDATA[
+$build->setView($view);
+]]></programlisting>
+            </sect4>
+
+            <sect4 id="zend.dojo.build-layers.usage.options.layername">
+                <title>Setting the layer name</title>
+
+                <para>
+                    While the layer name may be passed during instantiation,
+                    you may also pass it in to an instance via the
+                    <methodname>setLayerName()</methodname> method:
+                </para>
+
+                <programlisting role="php"><![CDATA[
+$build->setLayerName("custom.main");
+]]></programlisting>
+            </sect4>
+
+            <sect4 id="zend.dojo.build-layers.usage.options.onload">
+                <title>Including onLoad events in the generated layer</title>
+
+                <para>
+                    <code>dojo.addOnLoad</code> is a useful utility for
+                    specifying actions that should trigger when the DOM has
+                    finished loading.  The <code>dojo()</code> view helper can
+                    create these statements via its
+                    <methodname>addOnLoad()</methodname> and
+                    <methodname>onLoadCapture*()</methodname> methods. In some
+                    cases, it makes sense to push these into your layer file
+                    instead of rendering them via your view scripts.
+                </para>
+
+                <para>
+                    By default, these are not rendered; to enable them, pass the
+                    <code>consumeOnLoad</code> configuration key during
+                    instantiation:
+                </para>
+
+                <programlisting role="php"><![CDATA[
+$build = new Zend_Dojo_BuildLayer(array(
+    'view'          => $view,
+    'layerName'     => 'custom.main',
+    'consumeOnLoad' => true,
+));
+]]></programlisting>
+
+                <para>
+                    Alternately, you can use the
+                    <methodname>setConsumeOnLoad()</methodname> method after
+                    instantiation:
+                </para>
+
+                <programlisting role="php"><![CDATA[
+$build->setConsumeOnLoad(true);
+]]></programlisting>
+            </sect4>
+
+            <sect4 id="zend.dojo.build-layers.usage.options.javascript">
+                <title>Including captured JavaScript in the generated layer</title>
+
+                <para>
+                    The <code>dojo()</code> view helper includes methods for
+                    capturing arbitrary JavaScript to include in the
+                    &lt;script&gt; tag containing the various
+                    <code>dojo.require</code> and <code>dojo.addOnLoad</code>
+                    statements. This can be useful when creating default data
+                    stores or globally scoped objects used throughout your
+                    application.
+                </para>
+
+                <para>
+                    By default, these are not rendered; to enable them, pass the
+                    <code>consumeJavascript</code> configuration key during
+                    instantiation:
+                </para>
+
+                <programlisting role="php"><![CDATA[
+$build = new Zend_Dojo_BuildLayer(array(
+    'view'              => $view,
+    'layerName'         => 'custom.main',
+    'consumeJavascript' => true,
+));
+]]></programlisting>
+
+                <para>
+                    Alternately, you can use the
+                    <methodname>setConsumeJavascript()</methodname> method after
+                    instantiation:
+                </para>
+
+                <programlisting role="php"><![CDATA[
+$build->setConsumeJavascript(true);
+]]></programlisting>
+            </sect4>
+        </sect3>
+    </sect2>
+
+    <sect2 id="zend.dojo.build-layers.profiles">
+        <title>Generating Build Profiles with Zend_Dojo_BuildLayer</title>
+
+        <para>
+            One of the chief benefits of a Dojo module layer is that it
+            facilitates the creation of a custom build.
+            <classname>Zend_Dojo_BuildLayer</classname> has functionality for
+            generate build profiles.
+        </para>
+
+        <para>
+            The simplest use case is to utilize the
+            <methodname>generateBuildProfile()</methodname> method and send the
+            output to a file:
+        </para>
+
+        <programlisting role="php"><![CDATA[
+$build = new Zend_Dojo_BuildLayer(array(
+    'view'      => $view,
+    'layerName' => 'custom.main',
+));
+
+$profile   = $build->generateBuildProfile();
+$filename  = APPLICATION_PATH . '/../misc/scripts/custom.profile.js';
+file_put_contents($filename, $profile);
+]]></programlisting>
+
+        <para>
+            Just like generating layers, you may want to automate this via a
+            <methodname>dispatchLoopShutdown()</methodname> plugin hook; you
+            could even simply modify the one shown for generating layers to read
+            as follows:
+        </para>
+
+        <programlisting role="php"><![CDATA[
+class App_Plugin_DojoLayer extends Zend_Controller_Plugin_Abstract
+{
+    public $layerScript  = APPLICATION_PATH . '/../public/js/custom/main.js';
+    public $buildProfile = APPLICATION_PATH . '/../misc/scripts/custom.profile.js';
+    protected $_build;
+
+    public function dispatchLoopShutdown()
+    {
+        if (!file_exists($this->layerScript)) {
+            $this->generateDojoLayer();
+        }
+        if (!file_exists($this->buildProfile)) {
+            $this->generateBuildProfile();
+        }
+    }
+
+    public function generateDojoLayer() { /* ... */ }
+
+    public function generateBuildProfile()
+    {
+        $profile = $this->getBuild()->generateBuildProfile();
+        file_put_contents($this->buildProfile, $profile);
+    }
+
+}
+]]></programlisting>
+
+        <para>
+            As noted, with module layers, you should only create the file once.
+        </para>
+
+        <sect3 id="zend.dojo.build-layers.profiles.options">
+            <title>Build Profile options</title>
+
+            <para>
+                The above functionality will suffice for most situations. The
+                only way to customize build profile generation is to provide
+                additional build profile options to utilize.
+            </para>
+
+            <para>
+                As an example, you may want to specify what type of
+                optimizations should be performed, whether or not to optimize
+                CSS files in the layer, whether or not to copy tests into the
+                build, etc. For a listing of available options, you should read
+                the <ulink url="http://docs.dojocampus.org/build/index">Dojo
+                    Build documentation</ulink> and <ulink
+                    url="http://www.dojotoolkit.org/book/dojo-book-0-9/part-4-meta-dojo/package-system-and-custom-builds">accompanying
+                documentation</ulink>.
+            </para>
+
+            <para>
+                Setting these options is trivial: use the
+                <methodname>addProfileOption()</methodname>,
+                <methodname>addProfileOptions()</methodname>, or
+                <methodname>setProfileOptions()</methodname> methods. The first
+                method adds a single key/value option pair, the second will add
+                several, and the third will overwrite any options with the list
+                of key/value pairs provided.
+            </para>
+
+            <para>
+                By default, the following options are set:
+            </para>
+
+            <programlisting role="javascript"><![CDATA[
+{
+    action:        "release",
+    optimize:      "shrinksafe",
+    layerOptimize: "shrinksafe",
+    copyTests:     false,
+    loader:        "default",
+    cssOptimize:   "comments"
+}
+]]></programlisting>
+
+            <para>
+                You can pass in whatever key/value pairs you want; the Dojo
+                build script will ignore those it does not understand.
+            </para>
+
+            <para>
+                As an example of setting options:
+            </para>
+
+            <programlisting role="php"><![CDATA[
+// A single option:
+$build->addProfileOption('version', 'zend-1.3.1');
+
+// Several options:
+$build->addProfileOptions(array(
+    'loader'   => 'xdomain',
+    'optimize' => 'packer',
+));
+
+// Or overwrite options:
+$build->setProfileOptions(array(
+    'version'  => 'custom-1.3.1',
+    'loader'   => 'shrinksafe',
+    'optimize' => 'shrinksafe',
+));
+]]></programlisting>
+        </sect3>
+    </sect2>
+</sect1>

+ 306 - 0
library/Zend/Dojo/BuildLayer.php

@@ -0,0 +1,306 @@
+<?php
+class Zend_Dojo_BuildLayer
+{
+    protected $_consumeJavascript = false;
+
+    protected $_consumeOnLoad = false;
+
+    protected $_dojo;
+
+    protected $_layerName;
+
+    protected $_layerScriptPath;
+
+    protected $_profileOptions = array(
+        'action'        => 'release',
+        'optimize'      => 'shrinksafe',
+        'layerOptimize' => 'shrinksafe',
+        'copyTests'     => false,
+        'loader'        => 'default',
+        'cssOptimize'   => 'comments',
+    );
+
+    protected $_profilePath;
+
+    protected $_profilePrefixes = array();
+
+    protected $_view;
+
+    public function __construct($options = null)
+    {
+        if (null !== $options) {
+            if ($options instanceof Zend_Config) {
+                $options = $options->toArray();
+            } elseif (!is_array($options)) {
+                require_once 'Zend/Dojo/Exception.php';
+                throw new Zend_Dojo_Exception('Invalid options provided to constructor');
+            }
+            $this->setOptions($options);
+        }
+    }
+
+    public function setOptions(array $options)
+    {
+        $methods = get_class_methods($this);
+        foreach ($options as $key => $value) {
+            $method = 'set' . ucfirst($key);
+            if (in_array($method, $methods)) {
+                $this->$method($value);
+            }
+        }
+        return $this;
+    }
+
+    public function setView(Zend_View_Interface $view)
+    {
+        $this->_view = $view;
+        return $this;
+    }
+
+    public function getView()
+    {
+        return $this->_view;
+    }
+
+    public function setDojoHelper(Zend_Dojo_View_Helper_Dojo_Container $helper)
+    {
+        $this->_dojo = $helper;
+        return $this;
+    }
+
+    public function getDojoHelper()
+    {
+        if (null === $this->_dojo) {
+            if (null === ($view = $this->getView())) {
+                require_once 'Zend/Dojo/Exception.php';
+                throw new Zend_Dojo_Exception('View object not registered; cannot retrieve dojo helper');
+            }
+            $helper = $view->getHelper('dojo');
+            $this->setDojoHelper($view->dojo());
+        }
+        return $this->_dojo;
+    }
+
+    public function setLayerName($name)
+    {
+        if (!preg_match('/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/i', $name)) {
+            require_once 'Zend/Dojo/Exception.php';
+            throw new Zend_Dojo_Exception('Invalid layer name provided; must be of form[a-z][a-z0-9_](\.[a-z][a-z0-9_])+');
+        }
+        $this->_layerName = $name;
+        return $this;
+    }
+
+    public function getLayerName()
+    {
+        return $this->_layerName;
+    }
+
+    public function setLayerScriptPath($path)
+    {
+        $this->_layerScriptPath = (string) $path;
+        return $this;
+    }
+
+    public function getLayerScriptPath()
+    {
+        return $this->_layerScriptPath;
+    }
+
+    public function setProfilePath($path)
+    {
+        $this->_profilePath = (string) $path;
+        return $this;
+    }
+
+    public function getProfilePath()
+    {
+        return $this->_profilePath;
+    }
+
+    public function setConsumeJavascript($flag)
+    {
+        $this->_consumeJavascript = (bool) $flag;
+        return $this;
+    }
+
+    public function consumeJavascript()
+    {
+        return $this->_consumeJavascript;
+    }
+
+    public function setConsumeOnLoad($flag)
+    {
+        $this->_consumeOnLoad = (bool) $flag;
+        return $this;
+    }
+
+    public function consumeOnLoad()
+    {
+        return $this->_consumeOnLoad;
+    }
+
+    public function setProfileOptions(array $options)
+    {
+        $this->_profileOptions += $options;
+        return $this;
+    }
+
+    public function addProfileOptions(array $options)
+    {
+        $this->_profileOptions = $this->_profileOptions + $options;
+        return $this;
+    }
+
+    public function addProfileOption($key, $value)
+    {
+        $this->_profileOptions[(string) $key] = $value;
+        return $this;
+    }
+
+    public function hasProfileOption($key)
+    {
+        return array_key_exists((string) $key, $this->_profileOptions);
+    }
+
+    public function getProfileOption($key)
+    {
+        if ($this->hasProfileOption($key)) {
+            return $this->_profileOptions[(string) $key];
+        }
+        return null;
+    }
+
+    public function getProfileOptions()
+    {
+        return $this->_profileOptions;
+    }
+
+    public function removeProfileOption($name)
+    {
+        if ($this->hasProfileOption($name)) {
+            unset($this->_profileOptions[(string) $name]);
+        }
+        return $this;
+    }
+
+    public function clearProfileOptions()
+    {
+        $this->_profileOptions = array();
+        return $this;
+    }
+
+    public function addProfilePrefix($prefix, $path = null)
+    {
+        if (null === $path) {
+            $path = '../' . $prefix;
+        }
+        $this->_profilePrefixes[$prefix] = array($prefix, $path);
+        return $this;
+    }
+
+    public function setProfilePrefixes(array $prefixes)
+    {
+        foreach ($prefixes as $prefix => $path) {
+            $this->addProfilePrefix($prefix, $path);
+        }
+        return $this;
+    }
+
+    public function getProfilePrefixes()
+    {
+        $layerName = $this->getLayerName();
+        if (null !== $layerName) {
+            $prefix    = $this->_getPrefix($layerName);
+            if (!array_key_exists($prefix, $this->_profilePrefixes)) {
+                $this->addProfilePrefix($prefix);
+            }
+        }
+        $view = $this->getView();
+        if (!empty($view)) {
+            $helper = $this->getDojoHelper();
+            if ($helper) {
+                $modules = $helper->getModules();
+                foreach ($modules as $module) {
+                    $prefix = $this->_getPrefix($module);
+                    if (!array_key_exists($prefix, $this->_profilePrefixes)) {
+                        $this->addProfilePrefix($prefix);
+                    }
+                }
+            }
+        }
+        return $this->_profilePrefixes;
+    }
+
+    public function generateLayerScript()
+    {
+        $helper        = $this->getDojoHelper();
+        $layerName     = $this->getLayerName();
+        $modulePaths   = $helper->getModulePaths();
+        $modules       = $helper->getModules();
+        $onLoadActions = $helper->getOnLoadActions();
+        $javascript    = $helper->getJavascript();
+
+        $content = 'dojo.provide("' . $layerName . '");' . "\n\n(function(){\n";
+
+        foreach ($modulePaths as $module => $path) {
+            $content .= sprintf("dojo.registerModulePath(\"%s\", \"%s\");\n", $module, $path);
+        }
+        foreach ($modules as $module) {
+            $content .= sprintf("dojo.require(\"%s\");\n", $module);
+        }
+
+        if ($this->consumeOnLoad()) {
+            foreach ($helper->getOnLoadActions() as $callback) {
+                $content .= sprintf("dojo.addOnLoad(%s);\n", $callback);
+            }
+        }
+        if ($this->consumeJavascript()) {
+            $javascript = implode("\n", $helper->getJavascript());
+            if (!empty($javascript)) {
+                $content .= "\n" . $javascript . "\n";
+            }
+        }
+
+        $content .= "})();";
+
+        return $content;
+    }
+
+    public function generateBuildProfile()
+    {
+        $profileOptions  = $this->getProfileOptions();
+        $layerName       = $this->getLayerName();
+        $layerScriptPath = $this->getLayerScriptPath();
+        $profilePrefixes = $this->getProfilePrefixes();
+
+        if (!array_key_exists('releaseName', $profileOptions)) {
+            $profileOptions['releaseName'] = substr($layerName, 0, strpos($layerName, '.'));
+        }
+
+        $profile = $profileOptions;
+        $profile['layers'] = array(array(
+            'name' => $layerScriptPath,
+            'layerDependencies' => array(),
+            'dependencies' => array($layerName),
+        ));
+        $profile['prefixes'] = array_values($profilePrefixes);
+
+        return 'dependencies = ' . $this->_filterJsonProfileToJavascript($profile) . ';';
+    }
+
+    protected function _getPrefix($module)
+    {
+        $segments  = explode('.', $module, 2);
+        return $segments[0];
+    }
+
+    protected function _filterJsonProfileToJavascript($profile)
+    {
+        require_once 'Zend/Json.php';
+        $profile = Zend_Json::encode($profile);
+        $profile = preg_replace('/"([^"]*)":/', '$1:', $profile);
+        $profile = preg_replace('/' . preg_quote('\\') . '/', '', $profile);
+        return $profile;
+    }
+}

+ 2 - 0
tests/Zend/Dojo/AllTests.php

@@ -29,6 +29,7 @@ if (!defined('PHPUnit_MAIN_METHOD')) {
     define('PHPUnit_MAIN_METHOD', 'Zend_Dojo_AllTests::main');
 }
 
+require_once 'Zend/Dojo/BuildLayerTest.php';
 require_once 'Zend/Dojo/DojoTest.php';
 require_once 'Zend/Dojo/DataTest.php';
 require_once 'Zend/Dojo/Form/AllTests.php';
@@ -52,6 +53,7 @@ class Zend_Dojo_AllTests
     {
         $suite = new PHPUnit_Framework_TestSuite('Zend Framework - Zend_Dojo');
 
+        $suite->addTestSuite('Zend_Dojo_BuildLayerTest');
         $suite->addTestSuite('Zend_Dojo_DojoTest');
         $suite->addTestSuite('Zend_Dojo_DataTest');
         $suite->addTest(Zend_Dojo_Form_AllTests::suite());

+ 385 - 0
tests/Zend/Dojo/BuildLayerTest.php

@@ -0,0 +1,385 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Dojo
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id: $
+ */
+
+/**
+ * Test helper
+ */
+require_once dirname(__FILE__) . '/../../TestHelper.php';
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Zend_Dojo_BuildLayerTest::main');
+}
+
+require_once 'Zend/Dojo/BuildLayer.php';
+require_once 'Zend/Dojo.php';
+require_once 'Zend/View.php';
+require_once 'Zend/Json.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Dojo
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Dojo_BuildLayerTest extends PHPUnit_Framework_TestCase
+{
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite(__CLASS__);
+        $result = PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    /**
+     * Sets up the fixture, for example, open a network connection.
+     * This method is called before a test is executed.
+     *
+     * @return void
+     */
+    public function setUp()
+    {
+        $this->view = new Zend_View();
+        Zend_Dojo::enableView($this->view);
+    }
+
+    public function testViewShouldBeNullByDefault()
+    {
+        $build = new Zend_Dojo_BuildLayer();
+        $this->assertNull($build->getView());
+    }
+
+    /**
+     * @expectedException Zend_Dojo_Exception
+     */
+    public function testRetrievingDojoHelperShouldRaiseExceptionWhenNoViewPresent()
+    {
+        $build = new Zend_Dojo_BuildLayer();
+        $build->getDojoHelper();
+    }
+
+    public function testDojoHelperShouldBeRetrievedFromViewObjectIfNotExplicitySet()
+    {
+        $build = new Zend_Dojo_BuildLayer(array('view' => $this->view));
+        $helper = $build->getDojoHelper();
+        $this->assertTrue($helper instanceof Zend_Dojo_View_Helper_Dojo_Container);
+    }
+
+    public function testLayerScriptPathIsNullByDefault()
+    {
+        $build = new Zend_Dojo_BuildLayer();
+        $this->assertNull($build->getLayerScriptPath());
+    }
+
+    public function testLayerScriptPathShouldBeMutable()
+    {
+        $build = new Zend_Dojo_BuildLayer();
+        $path  = __FILE__;
+        $build->setLayerScriptPath($path);
+        $this->assertEquals($path, $build->getLayerScriptPath());
+    }
+
+    public function testProfilePathIsNullByDefault()
+    {
+        $build = new Zend_Dojo_BuildLayer();
+        $this->assertNull($build->getProfilePath());
+    }
+
+    public function testProfilePathShouldBeMutable()
+    {
+        $build = new Zend_Dojo_BuildLayer();
+        $path  = __FILE__;
+        $build->setProfilePath($path);
+        $this->assertEquals($path, $build->getProfilePath());
+    }
+
+    public function testShouldNotConsumeJavascriptByDefault()
+    {
+        $build = new Zend_Dojo_BuildLayer();
+        $this->assertFalse($build->consumeJavascript());
+    }
+
+    public function testConsumeJavascriptFlagShouldBeMutable()
+    {
+        $build = new Zend_Dojo_BuildLayer();
+        $build->setConsumeJavascript(true);
+        $this->assertTrue($build->consumeJavascript());
+    }
+
+    public function testShouldNotConsumeOnLoadByDefault()
+    {
+        $build = new Zend_Dojo_BuildLayer();
+        $this->assertFalse($build->consumeOnLoad());
+    }
+
+    public function testConsumeOnLoadFlagShouldBeMutable()
+    {
+        $build = new Zend_Dojo_BuildLayer();
+        $build->setConsumeOnLoad(true);
+        $this->assertTrue($build->consumeOnLoad());
+    }
+
+    public function testLayerNameShouldBeNullByDefault()
+    {
+        $build = new Zend_Dojo_BuildLayer();
+        $this->assertNull($build->getLayerName());
+    }
+
+    public function testLayerNameShouldBeMutable()
+    {
+        $build = new Zend_Dojo_BuildLayer();
+        $build->setLayerName('custom.main');
+        $this->assertEquals('custom.main', $build->getLayerName());
+    }
+
+    /**
+     * @expectedException Zend_Dojo_Exception
+     */
+    public function testSettingLayerNameToInvalidFormatShouldRaiseException()
+    {
+        $build = new Zend_Dojo_BuildLayer();
+        $build->setLayerName('customFoo#bar');
+    }
+
+    public function testGeneratingLayerScriptShouldReturnValidLayerMarkup()
+    {
+        $this->view->dojo()->requireModule('dijit.form.Form')
+                           ->requireModule('dijit.form.TextBox')
+                           ->requireModule('dijit.form.Button');
+        $build = new Zend_Dojo_BuildLayer(array(
+            'view'      => $this->view,
+            'layerName' => 'foo.bar',
+        ));
+
+        $test   = $build->generateLayerScript();
+        $script = file_get_contents(dirname(__FILE__) . '/_files/BuildLayer.js');
+
+        $test   = $this->stripWhitespace($test);
+        $script = $this->stripWhitespace($script);
+        $this->assertEquals($script, $test);
+    }
+
+    public function testGeneratingLayerScriptWithOnLoadsEnabledShouldReturnValidLayerMarkup()
+    {
+        $this->view->dojo()->requireModule('dijit.form.Form')
+                           ->requireModule('dijit.form.TextBox')
+                           ->requireModule('dijit.form.Button')
+                           ->addOnLoad('custom.callback');
+        $build = new Zend_Dojo_BuildLayer(array(
+            'view'          => $this->view,
+            'layerName'     => 'foo.bar',
+            'consumeOnLoad' => true,
+        ));
+
+        $test   = $build->generateLayerScript();
+        $script = file_get_contents(dirname(__FILE__) . '/_files/BuildLayerOnLoad.js');
+
+        $test   = $this->stripWhitespace($test);
+        $script = $this->stripWhitespace($script);
+        $this->assertEquals($script, $test);
+    }
+
+    public function testGeneratingLayerScriptWithOnLoadsDisabledShouldNotRenderOnLoadEvents()
+    {
+        $this->view->dojo()->requireModule('dijit.form.Form')
+                           ->requireModule('dijit.form.TextBox')
+                           ->requireModule('dijit.form.Button')
+                           ->addOnLoad('custom.callback');
+        $build = new Zend_Dojo_BuildLayer(array(
+            'view'          => $this->view,
+            'layerName'     => 'foo.bar',
+        ));
+
+        $test   = $build->generateLayerScript();
+        $script = file_get_contents(dirname(__FILE__) . '/_files/BuildLayer.js');
+
+        $test   = $this->stripWhitespace($test);
+        $script = $this->stripWhitespace($script);
+        $this->assertEquals($script, $test);
+    }
+
+    public function testGeneratingLayerScriptWithJavascriptsEnabledShouldReturnValidLayerMarkup()
+    {
+        $this->view->dojo()->requireModule('dijit.form.Form')
+                           ->requireModule('dijit.form.TextBox')
+                           ->requireModule('dijit.form.Button')
+                           ->addJavascript('custom.callback();');
+        $build = new Zend_Dojo_BuildLayer(array(
+            'view'              => $this->view,
+            'layerName'         => 'foo.bar',
+            'consumeJavascript' => true,
+        ));
+
+        $test   = $build->generateLayerScript();
+        $script = file_get_contents(dirname(__FILE__) . '/_files/BuildLayerJavascript.js');
+
+        $test   = $this->stripWhitespace($test);
+        $script = $this->stripWhitespace($script);
+        $this->assertEquals($script, $test);
+    }
+
+    public function testGeneratingLayerScriptWithJavascriptsDisabledShouldNotRenderJavascripts()
+    {
+        $this->view->dojo()->requireModule('dijit.form.Form')
+                           ->requireModule('dijit.form.TextBox')
+                           ->requireModule('dijit.form.Button')
+                           ->addJavascript('custom.callback();');
+        $build = new Zend_Dojo_BuildLayer(array(
+            'view'          => $this->view,
+            'layerName'     => 'foo.bar',
+        ));
+
+        $test   = $build->generateLayerScript();
+        $script = file_get_contents(dirname(__FILE__) . '/_files/BuildLayer.js');
+
+        $test   = $this->stripWhitespace($test);
+        $script = $this->stripWhitespace($script);
+        $this->assertEquals($script, $test);
+    }
+
+    public function testProfileOptionsShouldIncludeSaneDefaultsByDefault()
+    {
+        $build = new Zend_Dojo_BuildLayer();
+        $expected = $this->getDefaultProfileOptions();
+        $options = $build->getProfileOptions();
+        $this->assertEquals($expected, $options);
+    }
+
+    public function testAddProfileOptionsShouldAddOptions()
+    {
+        $options = array('foo' => 'bar');
+        $build = new Zend_Dojo_BuildLayer(array(
+            'profileOptions' => $options,
+        ));
+        $build->addProfileOptions(array('bar' => 'baz'));
+        $expected = $this->getDefaultProfileOptions() + array('foo' => 'bar', 'bar' => 'baz');
+        $this->assertEquals($expected, $build->getProfileOptions());
+    }
+
+    public function testAddProfileOptionShouldAddOption()
+    {
+        $build = new Zend_Dojo_BuildLayer();
+        $build->addProfileOption('foo', 'bar');
+        $this->assertTrue($build->hasProfileOption('foo'));
+    }
+
+    public function testSetProfileOptionsShouldNotOverwriteOptions()
+    {
+        $options = array('foo' => 'bar');
+        $build = new Zend_Dojo_BuildLayer(array(
+            'profileOptions' => $options,
+        ));
+        $build->setProfileOptions(array('bar' => 'baz'));
+        $this->assertNotEquals(array('bar' => 'baz'), $build->getProfileOptions());
+        $this->assertTrue($build->hasProfileOption('bar'));
+    }
+
+    public function testProfilePrefixesAreEmptyByDefault()
+    {
+        $build = new Zend_Dojo_BuildLayer();
+        $prefixes = $build->getProfilePrefixes();
+        $this->assertTrue(empty($prefixes));
+    }
+
+    public function testProfilePrefixesIncludeLayerNamePrefix()
+    {
+        $build = new Zend_Dojo_BuildLayer(array('layerName' => 'foo.main'));
+        $prefixes = $build->getProfilePrefixes();
+        $this->assertTrue(array_key_exists('foo', $prefixes), var_export($prefixes, 1));
+        $this->assertEquals(array('foo', '../foo'), $prefixes['foo']);
+    }
+
+    public function testProfilePrefixesShouldIncludePrefixesOfAllRequiredModules()
+    {
+        $this->view->dojo()->requireModule('dijit.layout.TabContainer')
+                           ->requireModule('dojox.layout.ContentPane');
+        $build = new Zend_Dojo_BuildLayer(array('view' => $this->view));
+
+        $prefixes = $build->getProfilePrefixes();
+        $this->assertTrue(array_key_exists('dijit', $prefixes), var_export($prefixes, 1));
+        $this->assertEquals(array('dijit', '../dijit'), $prefixes['dijit']);
+        $this->assertTrue(array_key_exists('dojox', $prefixes), var_export($prefixes, 1));
+        $this->assertEquals(array('dojox', '../dojox'), $prefixes['dojox']);
+    }
+
+    public function testGeneratedDojoBuildProfileWithNoExtraLayerDependencies()
+    {
+        $build = new Zend_Dojo_BuildLayer(array(
+            'view' => $this->view,
+            'layerName' => 'zend.main',
+        ));
+        $profile  = $build->generateBuildProfile();
+        $expected = file_get_contents(dirname(__FILE__) . '/_files/BuildProfile.js');
+
+        $decodedProfile  = $this->decodeProfileJson($profile);
+        $decodedExpected = $this->decodeProfileJson($expected);
+
+        $this->assertEquals($decodedExpected, $decodedProfile, 'Expected: ' . $expected . "\nReceived: " . $profile . "\n");
+    }
+
+    public function testGeneratedDojoBuildProfileWithLayerDependencies()
+    {
+        $this->view->dojo()->requireModule('dijit.layout.BorderContainer')
+                           ->requireModule('dojox.layout.ContentPane');
+        $build = new Zend_Dojo_BuildLayer(array(
+            'view' => $this->view,
+            'layerName' => 'zend.main',
+        ));
+        $profile  = $build->generateBuildProfile();
+        $expected = file_get_contents(dirname(__FILE__) . '/_files/BuildProfileWithDependencies.js');
+
+        $decodedProfile  = $this->decodeProfileJson($profile);
+        $decodedExpected = $this->decodeProfileJson($expected);
+
+        $this->assertEquals($decodedExpected, $decodedProfile, 'Expected: ' . $expected . "\nReceived: " . $profile . "\n");
+    }
+
+    protected function stripWhitespace($string)
+    {
+        $string = preg_replace('/^[ ]+/m', '', $string);
+        $string = preg_replace('/([ ]{2,})/s', ' ', $string);
+        $string = preg_replace('/(\r|\r\n|\n){2, }/s', "\n", $string);
+        $string = preg_replace('/(\r|\r\n|\n)$/', '', $string);
+        return $string;
+    }
+
+    protected function getDefaultProfileOptions()
+    {
+        return array(
+            'action'        => 'release',
+            'optimize'      => 'shrinksafe',
+            'layerOptimize' => 'shrinksafe',
+            'copyTests'     => false,
+            'loader'        => 'default',
+            'cssOptimize'   => 'comments',
+        );
+    }
+
+    protected function decodeProfileJson($profile)
+    {
+        $profile = preg_replace('/^dependencies = (.*?);$/s', '$1', $profile);
+        return Zend_Json::decode($profile);
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Dojo_BuildLayerTest::main') {
+    Zend_Dojo_BuildLayerTest::main();
+}

+ 7 - 0
tests/Zend/Dojo/_files/BuildLayer.js

@@ -0,0 +1,7 @@
+dojo.provide("foo.bar");
+
+(function(){
+    dojo.require("dijit.form.Form");
+    dojo.require("dijit.form.TextBox");
+    dojo.require("dijit.form.Button");
+})();

+ 9 - 0
tests/Zend/Dojo/_files/BuildLayerJavascript.js

@@ -0,0 +1,9 @@
+dojo.provide("foo.bar");
+
+(function(){
+    dojo.require("dijit.form.Form");
+    dojo.require("dijit.form.TextBox");
+    dojo.require("dijit.form.Button");
+
+    custom.callback();
+})();

+ 8 - 0
tests/Zend/Dojo/_files/BuildLayerOnLoad.js

@@ -0,0 +1,8 @@
+dojo.provide("foo.bar");
+
+(function(){
+    dojo.require("dijit.form.Form");
+    dojo.require("dijit.form.TextBox");
+    dojo.require("dijit.form.Button");
+    dojo.addOnLoad(custom.callback);
+})();

+ 21 - 0
tests/Zend/Dojo/_files/BuildProfile.js

@@ -0,0 +1,21 @@
+dependencies = {
+    action:        "release",
+    releaseName:   "zend",
+    loader:        "default",
+    cssOptimize:   "comments",
+    optimize:      "shrinksafe",
+    layerOptimize: "shrinksafe",
+    copyTests:     false,
+    layers: [
+        {
+            name: "../zend/main.js",
+            layerDependencies: [],
+            dependencies: [
+                "zend.main",
+            ]
+        }
+    ],
+    prefixes: [
+        [ "zend", "../zend" ]
+    ]
+};

+ 23 - 0
tests/Zend/Dojo/_files/BuildProfileWithDependencies.js

@@ -0,0 +1,23 @@
+dependencies = {
+    action:        "release",
+    releaseName:   "zend",
+    loader:        "default",
+    cssOptimize:   "comments",
+    optimize:      "shrinksafe",
+    layerOptimize: "shrinksafe",
+    copyTests:     false,
+    layers: [
+        {
+            name: "../zend/main.js",
+            layerDependencies: [],
+            dependencies: [
+                "zend.main",
+            ]
+        }
+    ],
+    prefixes: [
+        [ "dijit", "../dijit" ],
+        [ "dojox", "../dojox" ],
+        [ "zend", "../zend" ]
+    ]
+};