diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b0672a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +composer.lock +vendor/* +tests/test_files/webroot/css/* diff --git a/composer.json b/composer.json index 03381e4..e4fc1cc 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,6 @@ "license": "Apache-2.0", "require": { "php": ">=5.4.19", - "cakephp/plugin-installer": "*", "cakephp/cakephp": "3.0.x-dev", "oyejorge/less.php": "1.7.0.2" }, diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..973fab8 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,11 @@ + + + + + + ./tests/ + + + diff --git a/src/View/Helper/LessHelper.php b/src/View/Helper/LessHelper.php index 2325e41..21a655e 100644 --- a/src/View/Helper/LessHelper.php +++ b/src/View/Helper/LessHelper.php @@ -8,6 +8,7 @@ namespace Less\View\Helper; use Cake\Core\Plugin; +use Cake\Core\Configure; use Cake\Log\Log; use Cake\View\View; use Cake\Routing\Router; @@ -15,6 +16,9 @@ class LessHelper extends Helper { +/** + * {@inheritdoc} + */ public $helpers = [ 'Html', 'Url' ]; @@ -54,28 +58,32 @@ class LessHelper extends Helper /** * Initializes Lessc and cleans less and css paths + * + * {@inheritdoc} */ public function __construct(View $View, array $config = []) { parent::__construct($View, $config); // Initialize oyejorge/less.php parser - require ROOT . DS . 'vendor' . DS . 'oyejorge' . DS . 'less.php' . DS . 'lib' . DS . 'Less' . DS . 'Autoloader.php'; + require_once ROOT . DS . 'vendor' . DS . 'oyejorge' . DS . 'less.php' . DS . 'lib' . DS . 'Less' . DS . 'Autoloader.php'; \Less_Autoloader::register(); - $this->css_path = trim($this->css_path, '/'); + $this->css_path = WWW_ROOT . trim($this->css_path, '/'); } /** - * Compile the less and return a css tag. - * In case of error, it will load less with javascript - * instead of returning the resulting css tag. + * Compiles any less files passed and returns the compiled css. + * In case of error, it will load less with the javascritp parser so you'll be + * able to see any errors on screen. If not, check out the error.log file in your + * CakePHP's logs folder. * - * @param mixed $less The input .less file to be compiled or an array of .less files - * @param array $parser_options The options to be passed to the less php compiler - * @param array $lessjs_options An array of options to be passed as a json to the less javascript object. - * @return string The resulting tag for the compiled css, or the tag for the .less & less.min if compilation fails - * @throws Exception + * @param mixed $less The input .less file to be compiled or an array + * of .less files + * @param array $options Options in 'js' key will be pased to the less.js + * parser and options in 'parser' will be passed to the less.php parser + * @param array $modify_vars Array of modify vars + * @return string */ public function less($less = 'styles.less', array $options = [], array $modify_vars = []) { @@ -96,17 +104,21 @@ public function less($less = 'styles.less', array $options = [], array $modify_v } return $this->Html->css($css); } - catch (Exception $e) { - $this->error = $e->getMessage(); + catch (\Exception $e) { + // env must be development in order to see errors on-screen + if (Configure::read('debug')) { + $options['js']['env'] = 'development'; + } - Log::write('warning', "Error compiling less file: " . $this->error); + $this->error = $e->getMessage(); + Log::write('error', "Error compiling less file: " . $this->error); return $this->jsBlock($less, $options); } } /** - * Returns the initialization string for less (javascript based) + * Returns the required script and link tags to get less.js working * * @param string $less The input .less file to be loaded * @param array $options An array of options to be passed to the `less` configuration var @@ -132,16 +144,18 @@ public function jsBlock($less, array $options = []) /** * Compiles an input less file to an output css file using the PHP compiler - * - * @param string $input The input .less file to be compiled - * @param string $output The output .css file, resulting from the compilation - * @return boolean true on success, false otherwise + * @param array $input The input .less files to be compiled + * @param array $options Options to be passed to the php parser + * @param array $modify_vars Less modify_vars + * @param boolean $cache Whether to cache or not + * @return string If cache is not enabled will return the full CSS compiled. + * Otherwise it will return the resulting filename from the compilation. */ - public function compile($input, array $options = [], array $modify_vars = [], $cache = true) + public function compile(array $input, array $options = [], array $modify_vars = [], $cache = true) { $to_parse = []; foreach ($input as $in) { - $less = realpath($in); + $less = realpath(WWW_ROOT . $in); // If we have plugin notation, ensure to properly load the files list($plugin, $name) = $this->_View->pluginSplit($in, false); if (!empty($plugin)) { @@ -159,11 +173,14 @@ public function compile($input, array $options = [], array $modify_vars = [], $c } $lessc = new \Less_Parser($options); - $lessc->ModifyVars($modify_vars); foreach ($to_parse as $file => $path) { $lessc->parseFile($file, $path); } + // ModifyVars must be called at the bottom of the parsing, + // this way we're ensuring they override their default values. + // http://lesscss.org/usage/#command-line-usage-modify-variable + $lessc->ModifyVars($modify_vars); return $lessc->getCss(); } @@ -172,6 +189,9 @@ public function compile($input, array $options = [], array $modify_vars = [], $c * Sets the less configuration var options based on the ones given by the user * and our default ones. * + * Here's also where we define the import_callback used by less.php parser, + * so it can find files successfully even if they're on plugin folders. + * * @param array $options An array of options containing our options combined with the ones for the parsers * @return array $options The resulting $options array */ @@ -226,17 +246,17 @@ private function setOptions(array $options) return $options; } - /** - * Returns tha full base url for the given asset - * - * @param string $asset The asset path - * @param string $plugin Plugin where the asset resides - * @return string - */ - private function assetBaseUrl($asset, $plugin = 'Bootstrap') +/** + * Returns tha full base url for the given asset + * + * @param string $asset The asset path + * @param string $plugin Plugin where the asset resides + * @return string + */ + private function assetBaseUrl($asset, $plugin) { $dir = dirname($asset); - $path = !empty($dir) ? "/$dir" : null; + $path = !empty($dir) && $dir != '.' ? "/$dir" : null; return $this->Url->assetUrl($plugin . $path, [ 'fullBase' => true diff --git a/tests/TestCase/View/Helper/LessHelperTest.php b/tests/TestCase/View/Helper/LessHelperTest.php new file mode 100644 index 0000000..9c9203f --- /dev/null +++ b/tests/TestCase/View/Helper/LessHelperTest.php @@ -0,0 +1,119 @@ +Less = new LessHelper($View); + } + + public function testLess() + { + $options = [ + 'cache' => false + ]; + + // [Less.php] Basic compiling + $result = $this->Less->less('less/test.less', $options, ['bgcolor' => 'red']); + $this->assertHtml([ + ['style' => true], + 'body{background-color: #f00}', + '/style' + ], $result); + + // [Less.php] Compiling using cache (here we only check for the + // resulting tag, as the compilation checks are made in testCompile) + $result = $this->Less->less('less/test.less'); + $this->assertHtml([ + 'link' => [ + 'rel' => 'stylesheet', + 'href' => 'preg:/\/css\/lessphp_[0-9a-z]+\.css/' + ] + ], $result); + + // Trying the js fallback + $result = $this->Less->less('less/test_error.less'); + $this->assertHtml([ + 'link' => [ + 'rel' => 'stylesheet/less', + 'href' => '/less/test_error.less' + ], + /*['script' => true], + // Need some help here :p + '/script', + 'script' => [ + 'src' => '/less/js/less.min.js' + ]*/ + ], $result); + } + + // public function testJsBlock() + // { + + // } + + public function testCompile() + { + $options = [ + 'compress' => true + ]; + + // Basic compiling + $result = $this->Less->compile(['less/test.less'], $options, [], false); + $this->assertTextEquals('body{background-color: #000}', $result); + + // Changing the bgcolor var + $result = $this->Less->compile(['less/test.less'], $options, ['bgcolor' => 'magenta'], false); + $this->assertTextEquals('body{background-color: #f0f}', $result); + + // Compiling with cache + $result = $this->Less->compile(['less/test.less'], $options); + $this->assertRegExp('/lessphp_[a-z0-9]+\.css/', $result); + $result = file_get_contents(WWW_ROOT . 'css' . DS . $result); + $this->assertTextEquals('body{background-color: #000}', $result); + + // Compiling with cache and modify_vars + $result = $this->Less->compile(['less/test.less'], $options, ['bgcolor' => 'darkorange']); + $this->assertRegExp('/lessphp_[a-z0-9]+\.css/', $result); + $result = file_get_contents(WWW_ROOT . 'css' . DS . $result); + $this->assertTextEquals('body{background-color: #ff8c00}', $result); + } + + public function testAssetBaseUrl() + { + $assetBaseUrl = self::getProtectedMethod('assetBaseUrl'); + $result = $assetBaseUrl->invokeArgs($this->Less, [ + 'less/styles.less', + 'Less' + ]); + $this->assertEquals('http://localhost/Less/less', $result); + + $result = $assetBaseUrl->invokeArgs($this->Less, [ + 'css/whatever.less', + 'Bootstrap' + ]); + $this->assertEquals('http://localhost/Bootstrap/css', $result); + + $result = $assetBaseUrl->invokeArgs($this->Less, [ + 'whatever.less', + 'Less' + ]); + $this->assertEquals('http://localhost/Less', $result); + } + + protected static function getProtectedMethod($name) { + $class = new \ReflectionClass('Less\View\Helper\LessHelper'); + $method = $class->getMethod($name); + $method->setAccessible(true); + return $method; + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..3642fbc --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,65 @@ + 'App', + 'encoding' => 'UTF-8', + 'base' => false, + 'baseUrl' => false, + 'dir' => 'src', + 'webroot' => WEBROOT_DIR, + 'www_root' => WWW_ROOT, + 'fullBaseUrl' => 'http://localhost', + 'imageBaseUrl' => 'img/', + 'jsBaseUrl' => 'js/', + 'cssBaseUrl' => 'css/', + 'paths' => [ + 'plugins' => [dirname(APP) . DS . 'plugins' . DS], + 'templates' => [APP . 'Template' . DS] + ] +]); + +Cache::config([ + '_cake_core_' => [ + 'engine' => 'File', + 'prefix' => 'cake_core_', + 'serialize' => true + ], + '_cake_model_' => [ + 'engine' => 'File', + 'prefix' => 'cake_model_', + 'serialize' => true + ] +]); + +Plugin::load('Less', ['path' => ROOT]); diff --git a/tests/test_files/webroot/css/.gitkeep b/tests/test_files/webroot/css/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_files/webroot/less/test.less b/tests/test_files/webroot/less/test.less new file mode 100644 index 0000000..76b38b0 --- /dev/null +++ b/tests/test_files/webroot/less/test.less @@ -0,0 +1,5 @@ +@bgcolor: black; + +body { + background-color: @bgcolor; +} diff --git a/tests/test_files/webroot/less/test_error.less b/tests/test_files/webroot/less/test_error.less new file mode 100644 index 0000000..1c3f741 --- /dev/null +++ b/tests/test_files/webroot/less/test_error.less @@ -0,0 +1,3 @@ +buggy { + +