diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 41fdf94..d40b036 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -12,12 +12,9 @@ build: php: version: "7.0.4" tests: - override: - - - command: "composer validate" override: - command: "php bin/phpunit -c phpunit.xml --colors=always --verbose --coverage-clover=coverage.xml" coverage: file: "coverage.xml" - format: "php-clover" + format: "clover" diff --git a/.travis.yml b/.travis.yml index 97dda12..8353067 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ php: - "5.5" - "5.6" - "7.0" + - "7.1" - "hhvm" before_script: diff --git a/README.md b/README.md index 150a8c8..b550fe1 100644 --- a/README.md +++ b/README.md @@ -2,25 +2,24 @@ [![codecov](https://codecov.io/gh/learn-symfony/css-compiler/branch/master/graph/badge.svg)](https://codecov.io/gh/learn-symfony/css-compiler) [![SensioLabsInsight](https://insight.sensiolabs.com/projects/b72078dc-94a7-492f-9deb-3829c41d2519/mini.png)](https://insight.sensiolabs.com/projects/b72078dc-94a7-492f-9deb-3829c41d2519) +[![HHVM Status](http://hhvm.h4cc.de/badge/eugene-matvejev/css-compiler.svg)](http://hhvm.h4cc.de/package/eugene-matvejev/css-compiler) + [![Latest Stable Version](https://poser.pugx.org/eugene-matvejev/css-compiler/version)](https://packagist.org/packages/eugene-matvejev/css-compiler) [![Total Downloads](https://poser.pugx.org/eugene-matvejev/css-compiler/downloads)](https://packagist.org/packages/eugene-matvejev/css-compiler) [![License](https://poser.pugx.org/eugene-matvejev/css-compiler/license)](https://packagist.org/packages/eugene-matvejev/css-compiler) [![composer.lock](https://poser.pugx.org/eugene-matvejev/css-compiler/composerlock)](https://packagist.org/packages/eugene-matvejev/css-compiler) -# PHP CSS Compiler -_can be triggered from composer's script's section: compiles LESS|SASS|Compass_ +# PHP CSS compiler with composer handler +_can be triggered from composer's script's section: compiles SCSS with compass|LESS_ -# How to use: -``` -composer require "eugene-matvejev/css-compiler" -``` +## how to use +`composer require eugene-matvejev/css-compiler` -### add callback into into composer's __scripts__: -``` -"EM\\CssCompiler\\ScriptHandler::generateCSS" -``` -_example_: +### add callback into into composer's __scripts__ +`"EM\\CssCompiler\\ScriptHandler::generateCSS"` + +_example_ ``` "scripts": { "post-update-cmd": "@custom-events", @@ -30,21 +29,22 @@ _example_: ] } ``` + ### add _css-compiler_ information inside of the _extra_ composer configuration * _format_: compression format * _input_: array of relative paths to the composer.json, all files will be picked up recursivly inside of the directory - * _output_: relative file path to the composer.json, where to save output (hard-copy) + * _output_: relative file path to the composer.json, where to save output (hard-copy) -_example_: +_example_ ``` "extra": { "css-compiler": [ { "format": "compact", "input": [ - "tests/shared-fixtures/scss" + "tests/shared-fixtures/compass/app.scss" ], - "output": "var/cache/assets/scss.css" + "output": "var/cache/assets/compass.css" }, { "format": "compact", @@ -52,13 +52,6 @@ _example_: "tests/shared-fixtures/sass" ], "output": "var/cache/assets/sass.css" - }, - { - "format": "compact", - "input": [ - "tests/shared-fixtures/compass/app.scss" - ], - "output": "var/cache/assets/compass.css" } ] } diff --git a/ScriptHandler.php b/ScriptHandler.php deleted file mode 100644 index 519c860..0000000 --- a/ScriptHandler.php +++ /dev/null @@ -1,17 +0,0 @@ -setSourcePath($sourcePath); - $this->outputPath = $outputPath; + $this + ->setInputPath($inputPath) + ->setOutputPath($outputPath); } /** @@ -62,7 +59,7 @@ public function getOutputPath() /** * @param string $path * - * @return File + * @return $this */ public function setOutputPath($path) { @@ -74,61 +71,37 @@ public function setOutputPath($path) /** * @return string */ - public function getSourceContent() + public function getInputContent() { - return $this->sourceContent; + return $this->inputContent; } /** * @param string $content * - * @return File + * @return $this */ - public function setSourceContent($content) + public function setInputContent($content) { - $this->sourceContent = $content; + $this->inputContent = $content; return $this; } - /** - * @return File - * @throws FileException - */ - public function setSourceContentFromSourcePath() + public function getInputPath() { - $this->sourceContent = $this->readSourceContentByPath(); - - return $this; - } - - /** - * @return string - * @throws FileException - */ - protected function readSourceContentByPath() - { - if (!file_exists($this->getSourcePath())) { - throw new FileException("file: {$this->sourcePath} doesn't exists"); - } - - return file_get_contents($this->getSourcePath()); - } - - public function getSourcePath() - { - return $this->sourcePath; + return $this->inputPath; } /** * @param string $path * - * @return File + * @return $this */ - public function setSourcePath($path) + public function setInputPath($path) { - $this->sourcePath = $path; - $this->type = $this->detectSourceTypeFromPath($path); + $this->inputPath = $path; + $this->detectInputTypeByInputPath(); return $this; } @@ -136,19 +109,19 @@ public function setSourcePath($path) /** * @return string */ - public function getParsedContent() + public function getOutputContent() { - return $this->parsedContent; + return $this->outputContent; } /** * @param string $content * - * @return File + * @return $this */ - public function setParsedContent($content) + public function setOutputContent($content) { - $this->parsedContent = $content; + $this->outputContent = $content; return $this; } @@ -164,7 +137,7 @@ public function getType() /** * @param string $type * - * @return File + * @return $this */ public function setType($type) { @@ -173,17 +146,16 @@ public function setType($type) return $this; } - /** - * @param string $path - * - * @return string - */ - protected function detectSourceTypeFromPath($path) + protected function detectInputTypeByInputPath() { - $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + $extension = strtolower(pathinfo($this->getInputPath(), PATHINFO_EXTENSION)); - return in_array($extension, static::$supportedTypes) + $type = in_array($extension, static::$supportedTypes) ? $extension : static::TYPE_UNKNOWN; + + $this->setType($type); + + return $this; } } diff --git a/src/Processor/Processor.php b/src/Processor/Processor.php index 77521f1..2f72da0 100644 --- a/src/Processor/Processor.php +++ b/src/Processor/Processor.php @@ -5,11 +5,15 @@ use Composer\IO\IOInterface; use EM\CssCompiler\Container\FileContainer; use EM\CssCompiler\Exception\CompilerException; -use Leafo\ScssPhp\Compiler as SASSCompiler; +use EM\CssCompiler\Exception\FileException; +use Leafo\ScssPhp\Compiler as SCSSCompiler; +use Leafo\ScssPhp\Exception\ParserException; use lessc as LESSCompiler; use scss_compass as CompassCompiler; /** + * @see ProcessorTest + * * @since 0.1 */ class Processor @@ -19,7 +23,7 @@ class Processor const FORMATTER_EXPANDED = 'expanded'; const FORMATTER_NESTED = 'nested'; const FORMATTER_COMPACT = 'compact'; - static $supportedFormatters = [ + public static $supportedFormatters = [ self::FORMATTER_COMPRESSED, self::FORMATTER_CRUNCHED, self::FORMATTER_EXPANDED, @@ -35,9 +39,9 @@ class Processor */ private $files = []; /** - * @var SASSCompiler + * @var SCSSCompiler */ - private $sass; + private $scss; /** * @var LESSCompiler */ @@ -46,15 +50,10 @@ class Processor public function __construct(IOInterface $io) { $this->io = $io; - $this->initCompilers(); - } - - protected function initCompilers() - { $this->less = new LESSCompiler(); - $this->sass = new SASSCompiler(); - /** attaches compass functionality to the SASS compiler */ - new CompassCompiler($this->sass); + $this->scss = new SCSSCompiler(); + /** attaches compass functionality to the SCSS compiler */ + new CompassCompiler($this->scss); } /** @@ -98,7 +97,7 @@ protected function concatOutput() $outputMap[$file->getOutputPath()] = ''; } - $outputMap[$file->getOutputPath()] .= $file->getParsedContent(); + $outputMap[$file->getOutputPath()] .= $file->getOutputContent(); } return $outputMap; @@ -128,41 +127,72 @@ public function saveOutput() */ public function processFiles($formatter) { - $this->sass->setFormatter($this->getFormatterClass($formatter)); + $this->scss->setFormatter($this->getFormatterClass($formatter)); $this->io->write("use '{$formatter}' formatting"); foreach ($this->files as $file) { - $this->io->write("processing: {$file->getSourcePath()}"); - $file->setSourceContentFromSourcePath(); + $this->io->write("processing: {$file->getInputPath()}"); + $this->fetchInputContextIntoFile($file); try { $this->processFile($file); } catch (CompilerException $e) { - $this->io->writeError("failed to process: {$file->getSourcePath()}"); + $this->io->writeError("failed to process: {$file->getOutputPath()}"); } } } /** - * @param File $file + * @param FileContainer $file * - * @return File + * @return FileContainer * @throws CompilerException */ public function processFile(FileContainer $file) { switch ($file->getType()) { - case FileContainer::TYPE_COMPASS: case FileContainer::TYPE_SCSS: - case FileContainer::TYPE_SASS: - return $file->setParsedContent($this->sass->compile($file->getSourceContent())); + return $this->compileSCSS($file); case FileContainer::TYPE_LESS: - return $file->setParsedContent($this->less->compile($file->getSourceContent())); + return $this->compileLESS($file); } throw new CompilerException('unknown compiler'); } + /** + * @param FileContainer $file + * + * @return $this + * @throws CompilerException + */ + protected function compileSCSS(FileContainer $file) + { + try { + $this->scss->addImportPath(dirname($file->getInputPath())); + $content = $this->scss->compile($file->getInputContent()); + + return $file->setOutputContent($content); + } catch (ParserException $e) { + throw new CompilerException($e->getMessage(), 1, $e); + } + } + + /** + * @param FileContainer $file + * + * @return $this + * @throws CompilerException + */ + protected function compileLESS(FileContainer $file) + { + try { + return $file->setOutputContent($this->less->compileFile($file->getInputPath())); + } catch (\Exception $e) { + throw new CompilerException($e->getMessage(), 1, $e); + } + } + /** * @param string $formatter * @@ -176,4 +206,18 @@ protected function getFormatterClass($formatter) return 'Leafo\\ScssPhp\\Formatter\\' . ucfirst($formatter); } + + /** + * @param FileContainer $file + * + * @throws FileException + */ + protected function fetchInputContextIntoFile(FileContainer $file) + { + if (!file_exists($file->getInputPath())) { + throw new FileException("file: {$file->getInputPath()} doesn't exists"); + } + + $file->setInputContent(file_get_contents($file->getInputPath())); + } } diff --git a/src/ScriptHandler.php b/src/ScriptHandler.php index 3f204bb..732cc59 100644 --- a/src/ScriptHandler.php +++ b/src/ScriptHandler.php @@ -5,6 +5,11 @@ use Composer\Script\Event; use EM\CssCompiler\Processor\Processor; +/** + * @see ScriptHandlerTest + * + * @since 0.1 + */ class ScriptHandler { const CONFIG_MAIN_KEY = 'css-compiler'; @@ -13,11 +18,13 @@ class ScriptHandler const OPTION_KEY_FORMATTER = 'format'; const DEFAULT_OPTION_FORMATTER = 'compact'; protected static $mandatoryOptions = [ - self::OPTION_KEY_INPUT, - self::OPTION_KEY_OUTPUT + self::OPTION_KEY_INPUT => 'array', + self::OPTION_KEY_OUTPUT => 'string' ]; /** + * @api + * * @param Event $event * * @throws \InvalidArgumentException @@ -28,67 +35,73 @@ public static function generateCSS(Event $event) static::validateConfiguration($extra); $processor = new Processor($event->getIO()); - $currentDirectory = getcwd(); - foreach ($extra[static::CONFIG_MAIN_KEY] as $config) { - foreach ($config[static::OPTION_KEY_INPUT] as $value) { - $processor->attachFiles("{$currentDirectory}/{$value}", "{$currentDirectory}/{$config[static::OPTION_KEY_OUTPUT]}"); + foreach ($extra[static::CONFIG_MAIN_KEY] as $options) { + foreach ($options[static::OPTION_KEY_INPUT] as $inputSource) { + $processor->attachFiles( + static::resolvePath($inputSource, getcwd()), + static::resolvePath($options[static::OPTION_KEY_OUTPUT], getcwd()) + ); } - $formatter = isset($config[static::OPTION_KEY_FORMATTER]) ? $config[static::OPTION_KEY_FORMATTER] : static::DEFAULT_OPTION_FORMATTER; - + $formatter = array_key_exists(static::OPTION_KEY_FORMATTER, $options) ? $options[static::OPTION_KEY_FORMATTER] : static::DEFAULT_OPTION_FORMATTER; $processor->processFiles($formatter); } $processor->saveOutput(); } + /** + * @param string $path + * @param string $prefix + * + * @return string + */ + protected static function resolvePath($path, $prefix) + { + return '/' === substr($path, 0, 1) ? $path : "{$prefix}/{$path}"; + } + /** * @param array $config * - * @return bool * @throws \InvalidArgumentException */ protected static function validateConfiguration(array $config) { - if (empty($config[static::CONFIG_MAIN_KEY])) { + if (!array_key_exists(static::CONFIG_MAIN_KEY, $config)) { throw new \InvalidArgumentException('compiler should needs to be configured through the extra.css-compiler setting'); } if (!is_array($config[static::CONFIG_MAIN_KEY])) { - throw new \InvalidArgumentException('the extra.css-compiler setting must be an array of objects'); + throw new \InvalidArgumentException('the extra.' . static::CONFIG_MAIN_KEY . ' setting must be an array of objects'); } - foreach ($config[static::CONFIG_MAIN_KEY] as $index => $el) { - if (!is_array($el)) { - throw new \InvalidArgumentException("the extra.css-compiler[{$index}]." . static::OPTION_KEY_INPUT . ' array'); + foreach ($config[static::CONFIG_MAIN_KEY] as $index => $options) { + if (!is_array($options)) { + throw new \InvalidArgumentException('extra.' . static::CONFIG_MAIN_KEY . "[$index] should be an array"); } - static::validateOptions($el); + static::validateMandatoryOptions($options, $index); } - - return true; } /** - * @param array $config + * @param array $options + * @param int $index * - * @return bool * @throws \InvalidArgumentException */ - protected static function validateOptions(array $config) + protected static function validateMandatoryOptions(array $options, $index) { - foreach (static::$mandatoryOptions as $option) { - if (empty($config[$option])) { - throw new \InvalidArgumentException("The extra.css-compiler[].{$option} required!"); + foreach (static::$mandatoryOptions as $optionIndex => $type) { + if (!array_key_exists($optionIndex, $options)) { + throw new \InvalidArgumentException('extra.' . static::CONFIG_MAIN_KEY . "[$index].{$optionIndex} is required!"); } - } - if (!is_array($config[static::OPTION_KEY_INPUT])) { - throw new \InvalidArgumentException('The extra.css-compiler[].' . static::OPTION_KEY_INPUT . ' should be array!'); - } - if (!is_string($config[static::OPTION_KEY_OUTPUT])) { - throw new \InvalidArgumentException('The extra.css-compiler[].' . static::OPTION_KEY_OUTPUT . ' should string!'); - } - return true; + $callable = "is_{$type}"; + if (!$callable($options[$optionIndex])) { + throw new \InvalidArgumentException('extra.' . static::CONFIG_MAIN_KEY . "[$index].{$optionIndex} should be {$type}!"); + } + } } } diff --git a/tests/phpunit/Container/File.php b/tests/phpunit/Container/File.php deleted file mode 100644 index c0fbcee..0000000 --- a/tests/phpunit/Container/File.php +++ /dev/null @@ -1,7 +0,0 @@ -invokeConstructor('input', 'output', FileContainer::TYPE_UNKNOWN); + } + + /** + * @see FileContainer::__constuct + * @see FileContainer::TYPE_SCSS + * + * @test + */ + public function constructOnSCSSType() + { + $this->invokeConstructor('input.scss', 'output', FileContainer::TYPE_SCSS); + } + + /** + * @see FileContainer::__constuct + * @see FileContainer::TYPE_LESS + * + * @test + */ + public function constructOnLESSType() + { + $this->invokeConstructor('input.less', 'output', FileContainer::TYPE_LESS); + } + + /** + * as FileContainer can't exists without (in|out)put need to check that: + * (in|out)put paths assigned successfully + * (in|out)content is null + * type should not be null and be detected using @see FileContainer::detectInputTypeByInputPath + * + * @param string $inputPath + * @param string $outputPath + * @param string $expectedType + */ + private function invokeConstructor($inputPath, $outputPath, $expectedType) + { + $file = new FileContainer($inputPath, $outputPath); + + $this->assertEquals($inputPath, $file->getInputPath()); + $this->assertEquals($outputPath, $file->getOutputPath()); + + $this->assertNull($file->getOutputContent()); + $this->assertNull($file->getInputContent()); + + $this->assertNotNull($file->getType()); + $this->assertEquals($expectedType, $file->getType()); + } + + /** + * @see FileContainer::detectInputTypeByInputPath + * @test + */ + public function detectInputTypeByInputPath() + { + $inputPaths = [ + 'input.css' => FileContainer::TYPE_UNKNOWN, + 'input' => FileContainer::TYPE_UNKNOWN, + 'input.sass' => FileContainer::TYPE_UNKNOWN, + 'input.compass' => FileContainer::TYPE_UNKNOWN, + 'input.scss' => FileContainer::TYPE_SCSS, + 'input.less' => FileContainer::TYPE_LESS + ]; + + foreach ($inputPaths as $inputPath => $expectedType) { + $file = new FileContainer($inputPath, ''); + $this->assertEquals($expectedType, $file->getType()); + } + } +} diff --git a/tests/phpunit/Processor/ProcessorTest.php b/tests/phpunit/Processor/ProcessorTest.php index cadd335..16ad1c6 100644 --- a/tests/phpunit/Processor/ProcessorTest.php +++ b/tests/phpunit/Processor/ProcessorTest.php @@ -12,9 +12,7 @@ */ class ProcessorTest extends IntegrationTestSuite { - protected $event; protected $io; - protected $package; protected function setUp() { @@ -28,16 +26,16 @@ protected function setUp() public function attachFiles() { $paths = [ - static::getSharedFixturesDirectory() . '/sass', - static::getSharedFixturesDirectory() . '/compass' + static::getSharedFixturesDirectory() . '/less' => 1, + static::getSharedFixturesDirectory() . '/compass' => 1, + static::getSharedFixturesDirectory() . '/scss/layout.scss' => 1, + static::getSharedFixturesDirectory() . '/scss' => 4, ]; - $cacheDir = dirname(dirname(__DIR__)) . '/var/cache'; - - foreach ($paths as $path) { + foreach ($paths as $path => $expectedFiles) { $processor = new Processor($this->io); - $processor->attachFiles($path, $cacheDir); + $processor->attachFiles($path, ''); - $this->assertCount(2, $processor->getFiles()); + $this->assertCount($expectedFiles, $processor->getFiles()); } } @@ -49,27 +47,59 @@ public function attachFiles() */ public function attachFilesExpectedException() { - $path = static::getSharedFixturesDirectory() . '/do-not-exists'; - $cacheDir = dirname(dirname(__DIR__)) . '/var/cache'; + (new Processor($this->io))->attachFiles(static::getSharedFixturesDirectory() . '/do-not-exists', ''); + } - $processor = new Processor($this->io); - $processor->attachFiles($path, $cacheDir); + /** + * @see Processor::processFile + * @test + */ + public function processFileOnSCSS() + { + $this->invokeProcessFileMethod('scss/layout.scss', ''); + } - $this->assertCount(2, $processor->getFiles()); + /** + * @see Processor::processFile + * @test + */ + public function processFileOnLESS() + { + $this->invokeProcessFileMethod('less/print.less', ''); } /** * @see Processor::processFile * @test */ - public function processFileSASS() + public function processFileOnCompass() + { + $this->invokeProcessFileMethod('compass/compass-integration.scss', ''); + } + + /** + * @see Processor::processFile + * @test + */ + public function processFileOnImports() + { + $this->invokeProcessFileMethod('integration/app.scss', ''); + } + + /** + * @param string $inputPathPostfix + * @param string $outputPath + * + * @throws \EM\CssCompiler\Exception\CompilerException + */ + private function invokeProcessFileMethod($inputPathPostfix, $outputPath) { - $file = (new FileContainer(static::getSharedFixturesDirectory() . '/compass/sass/layout.scss', '')) - ->setSourceContentFromSourcePath(); + $file = new FileContainer(static::getSharedFixturesDirectory() . "/{$inputPathPostfix}", $outputPath); + $file->setInputContent(file_get_contents($file->getInputPath())); (new Processor($this->io))->processFile($file); - $this->assertNotEquals($file->getParsedContent(), $file->getSourceContent()); + $this->assertNotEquals($file->getInputContent(), $file->getOutputContent()); } /** @@ -80,9 +110,9 @@ public function processFileSASS() */ public function processFileExpectedException() { - $file = (new FileContainer(static::getSharedFixturesDirectory() . '/compass/sass/', '')) - ->setSourceContentFromSourcePath() - ->setType(FileContainer::TYPE_UNKNOWN); + $file = new FileContainer(static::getSharedFixturesDirectory() . '/compass', ''); + $file->setInputContent(file_get_contents($file->getInputPath())); + $file->setType(FileContainer::TYPE_UNKNOWN); (new Processor($this->io))->processFile($file); } @@ -91,7 +121,7 @@ public function processFileExpectedException() * @see Processor::getFormatterClass * @test */ - public function getFormatterClass() + public function getFormatterClassOnCorrect() { foreach (Processor::$supportedFormatters as $formatter) { $expected = 'Leafo\\ScssPhp\\Formatter\\' . ucfirst($formatter); @@ -102,4 +132,140 @@ public function getFormatterClass() ); } } + + /** + * @see Processor::getFormatterClass + * @test + * + * @expectedException \InvalidArgumentException + */ + public function getFormatterClassOnException() + { + $this->invokeMethod(new Processor($this->io), 'getFormatterClass', ['not-existing']); + } + + /** + * @see Processor::fetchInputContextIntoFile + * @test + */ + public function fetchInputContextIntoFileOnSuccess() + { + $file = new FileContainer(static::getSharedFixturesDirectory() . '/scss/layout.scss', ''); + $this->invokeMethod(new Processor($this->io), 'fetchInputContextIntoFile', [$file]); + + $this->assertNotNull($file->getInputContent()); + } + + /** + * @see Processor::fetchInputContextIntoFile + * @test + * + * @expectedException \EM\CssCompiler\Exception\FileException + */ + public function fetchInputContextIntoFileOnException() + { + $this->invokeMethod(new Processor($this->io), 'fetchInputContextIntoFile', [new FileContainer('input', 'output')]); + } + + /** + * @see Processor::processFiles + * @test + */ + public function processFilesOnSCSS() + { + $this->assertProcessFilesOnValid($this->getSharedFixturesDirectory() . '/scss', ''); + } + + /** + * @see Processor::processFiles + * @test + */ + public function processFilesOnNotValidSCSS() + { + $this->assertProcessFilesOnNotValid($this->getSharedFixturesDirectory() . '/not-valid-scss', ''); + } + + /** + * @see Processor::processFiles + * @test + */ + public function processFilesOnLESS() + { + $this->assertProcessFilesOnValid($this->getSharedFixturesDirectory() . '/less', ''); + } + + /** + * @see Processor::processFiles + * @test + */ + public function processFilesOnNotValidLESS() + { + $this->assertProcessFilesOnNotValid($this->getSharedFixturesDirectory() . '/not-valid-less', ''); + } + + /** + * @see Processor::processFiles + * + * @param string $input + * @param string $output + */ + private function assertProcessFilesOnValid($input, $output) + { + foreach ($this->processFiles($input, $output) as $file) { + $this->assertNotNull($file->getOutputContent()); + } + } + + /** + * @see Processor::processFiles + * + * @param string $input + * @param string $output + */ + private function assertProcessFilesOnNotValid($input, $output) + { + foreach ($this->processFiles($input, $output) as $file) { + $this->assertNull($file->getOutputContent()); + } + } + + /** + * @see Processor::processFiles + * + * @param string $input + * @param string $output + * + * @return FileContainer[] + */ + private function processFiles($input, $output) + { + $processor = new Processor($this->io); + + $processor->attachFiles($input, $output); + $processor->processFiles(Processor::FORMATTER_COMPRESSED); + + return $processor->getFiles(); + } + + /** + * @see ScriptHandler::processFiles + * @test + */ + public function saveOutput() + { + $processor = new Processor($this->io); + + $expectedOutputFile = $this->getCacheDirectory() . '/' . __FUNCTION__ . '.css'; + @unlink($expectedOutputFile); + + $processor->attachFiles( + $this->getSharedFixturesDirectory() . '/scss', + $expectedOutputFile + ); + $processor->processFiles(Processor::FORMATTER_COMPRESSED); + + $processor->saveOutput(); + + $this->assertFileExists($expectedOutputFile); + } } diff --git a/tests/phpunit/ScriptHandlerTest.php b/tests/phpunit/ScriptHandlerTest.php index 4736c42..3ba63c4 100644 --- a/tests/phpunit/ScriptHandlerTest.php +++ b/tests/phpunit/ScriptHandlerTest.php @@ -2,6 +2,11 @@ namespace EM\CssCompiler\Tests\PHPUnit; +use Composer\Composer; +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Package\RootPackage; +use Composer\Script\Event; use EM\CssCompiler\ScriptHandler; use EM\CssCompiler\Tests\Environment\IntegrationTestSuite; @@ -17,9 +22,9 @@ class ScriptHandlerTest extends IntegrationTestSuite * * @expectedException \InvalidArgumentException */ - function validateConfigurationExpectedExceptionOnNotExistingKey() + public function validateConfigurationExpectedExceptionOnNotExistingKey() { - $this->invokeMethod(new ScriptHandler(), 'validateConfiguration', [[]]); + $this->validateConfiguration([]); } /** @@ -28,9 +33,9 @@ function validateConfigurationExpectedExceptionOnNotExistingKey() * * @expectedException \InvalidArgumentException */ - function validateConfigurationExpectedExceptionOnNotArray() + public function validateConfigurationExpectedExceptionOnEmpty() { - $this->invokeMethod(new ScriptHandler(), 'validateConfiguration', [[ScriptHandler::CONFIG_MAIN_KEY]]); + $this->validateConfiguration([ScriptHandler::CONFIG_MAIN_KEY => '']); } /** @@ -39,96 +44,168 @@ function validateConfigurationExpectedExceptionOnNotArray() * * @expectedException \InvalidArgumentException */ - function validateConfigurationExpectedExceptionOptionIsNotArray() + public function validateConfigurationExpectedExceptionOnNotArray() { - $arr = [ - ScriptHandler::CONFIG_MAIN_KEY => [ - 'string' - ] - ]; - $this->invokeMethod(new ScriptHandler(), 'validateConfiguration', [$arr]); + $this->validateConfiguration([ScriptHandler::CONFIG_MAIN_KEY => 'string']); } /** * @see ScriptHandler::validateConfiguration * @test + * + * @expectedException \InvalidArgumentException */ - function validateConfigurationOnValid() + public function validateConfigurationExpectedExceptionOptionIsNotArray() { - $arr = [ + $this->validateConfiguration([ScriptHandler::CONFIG_MAIN_KEY => ['string']]); + } + + /** + * @see ScriptHandler::validateConfiguration + * @test + */ + public function validateConfigurationOnValid() + { + $args = [ ScriptHandler::CONFIG_MAIN_KEY => [ - [ - ScriptHandler::OPTION_KEY_INPUT => ['string'], - ScriptHandler::OPTION_KEY_OUTPUT => 'string' - ] + [ScriptHandler::OPTION_KEY_INPUT => ['string'], ScriptHandler::OPTION_KEY_OUTPUT => 'string'] ] ]; - $result = $this->invokeMethod(new ScriptHandler(), 'validateConfiguration', [$arr]); - $this->assertTrue($result); + + $this->assertNull($this->validateConfiguration($args)); } + /** + * @see ScriptHandler::validateConfiguration + * + * @param $args + * + * @return bool + */ + private function validateConfiguration($args) + { + return $this->invokeMethod(new ScriptHandler(), 'validateConfiguration', [$args]); + } /*** *************************** OPTIONS VALIDATION *************************** ***/ /** - * @see ScriptHandler::validateOptions + * @see ScriptHandler::validateMandatoryOptions * @test * * @expectedException \InvalidArgumentException */ - function validateOptionsExpectedExceptionOnMissingInput() + public function validateOptionsExpectedExceptionOnMissingInput() { - $this->invokeMethod(new ScriptHandler(), 'validateOptions', [[ScriptHandler::OPTION_KEY_OUTPUT]]); + $this->validateMandatoryOptions([[ScriptHandler::OPTION_KEY_OUTPUT => 'output']]); } /** - * @see ScriptHandler::validateOptions + * @see ScriptHandler::validateMandatoryOptions * @test * * @expectedException \InvalidArgumentException */ - function validateOptionsExpectedExceptionOnMissingOutput() + public function validateOptionsExpectedExceptionOnMissingOutput() { - $this->invokeMethod(new ScriptHandler(), 'validateOptions', [[ScriptHandler::OPTION_KEY_INPUT]]); + $this->validateMandatoryOptions([ScriptHandler::OPTION_KEY_INPUT => 'input']); } /** - * @see ScriptHandler::validateOptions + * @see ScriptHandler::validateMandatoryOptions * @test * * @expectedException \InvalidArgumentException */ - function validateOptionsExpectedExceptionOnInputNotArray() + public function validateOptionsExpectedExceptionOnInputNotArray() { - $this->invokeMethod(new ScriptHandler(), 'validateOptions', [[ + $this->validateMandatoryOptions([ ScriptHandler::OPTION_KEY_INPUT => 'string', ScriptHandler::OPTION_KEY_OUTPUT => 'string' - ]]); + ]); } /** - * @see ScriptHandler::validateOptions + * @see ScriptHandler::validateMandatoryOptions * @test * * @expectedException \InvalidArgumentException */ - function validateOptionsExpectedExceptionOnOutputNotString() + public function validateOptionsExpectedExceptionOnOutputNotString() { - $this->invokeMethod(new ScriptHandler(), 'validateOptions', [[ + $this->validateMandatoryOptions([ ScriptHandler::OPTION_KEY_INPUT => ['string'], ScriptHandler::OPTION_KEY_OUTPUT => ['string'] - ]]); + ]); } /** - * @see ScriptHandler::validateOptions + * @see ScriptHandler::validateMandatoryOptions * @test + * + * @group tester */ - function validateOptionsOnValid() + public function validateOptionsOnValid() { - $result = $this->invokeMethod(new ScriptHandler(), 'validateOptions', [[ - ScriptHandler::OPTION_KEY_INPUT => ['string'], - ScriptHandler::OPTION_KEY_OUTPUT => 'string' - ]]); + $this->assertNull( + $this->validateMandatoryOptions( + [ + ScriptHandler::OPTION_KEY_INPUT => ['string'], + ScriptHandler::OPTION_KEY_OUTPUT => 'string' + ] + ) + ); + } + + /** + * @see ScriptHandler::validateMandatoryOptions + * + * @param array $config + * + * @return bool + */ + private function validateMandatoryOptions($config) + { + return $this->invokeMethod(new ScriptHandler(), 'validateMandatoryOptions', [$config, 1]); + } + + /*** *************************** INTEGRATION *************************** ***/ + /** + * @see ScriptHandler::generateCSS + * @test + */ + public function generateCSS() + { + $composer = (new Composer()); + /** @var RootPackage|\PHPUnit_Framework_MockObject_MockObject $rootPackage */ + $rootPackage = $this->getMockBuilder(RootPackage::class) + ->setConstructorArgs(['css-compiler', 'dev-master', 'dev']) + ->setMethods(['getExtra']) + ->getMock(); + /** @var IOInterface|\PHPUnit_Framework_MockObject_MockObject $io */ + $io = $this->getMockBuilder(IOInterface::class)->getMock(); + + $output = $this->getCacheDirectory() . '/' . __FUNCTION__ . '.css'; + @unlink($output); + + $extra = [ + 'css-compiler' => [ + [ + 'format' => 'compact', + 'input' => [ + $this->getSharedFixturesDirectory() . '/less' + ], + 'output' => $output + ] + ] + ]; + + $rootPackage->expects($this->once()) + ->method('getExtra') + ->willReturn($extra); + $composer->setPackage($rootPackage); + + $event = new Event('onInstall', $composer, $io); - $this->assertTrue($result); + ScriptHandler::generateCSS($event); + $this->assertFileExists($output); } } diff --git a/tests/shared-enviroment/IntegrationTestSuite.php b/tests/shared-enviroment/IntegrationTestSuite.php index 50f6a23..f09c354 100644 --- a/tests/shared-enviroment/IntegrationTestSuite.php +++ b/tests/shared-enviroment/IntegrationTestSuite.php @@ -30,7 +30,7 @@ protected function invokeMethod($object, $methodName, array $methodArguments = [ /** * @return string */ - public static function getRootDirectory() + protected function getRootDirectory() { return dirname(__DIR__); } @@ -38,7 +38,7 @@ public static function getRootDirectory() /** * @return string */ - public static function getSharedFixturesDirectory() + protected function getSharedFixturesDirectory() { return static::getRootDirectory() . '/shared-fixtures'; } @@ -50,8 +50,13 @@ public static function getSharedFixturesDirectory() * * @return string */ - public static function getSharedFixtureContent(string $filename) + protected function getSharedFixtureContent(string $filename) { return file_get_contents(static::getSharedFixturesDirectory() . "/$filename"); } + + protected function getCacheDirectory() + { + return dirname($this->getRootDirectory()) . '/var/cache/tests'; + } } diff --git a/tests/shared-fixtures/compass/app.scss b/tests/shared-fixtures/compass/app.scss deleted file mode 100644 index 28ded83..0000000 --- a/tests/shared-fixtures/compass/app.scss +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Welcome to Compass. - * In this file you should write your main styles. (or centralize your imports) - * Import this file using the following HTML or equivalent: - * - */ - -/* @import "compass/reset"; */ - -@import "compass/reset"; -@import "sass/layout"; -@import "sass/layout-loading-animation"; -//@import "layout-loading-animation"; -// -//@import "game"; -//@import "game-results"; diff --git a/tests/shared-fixtures/compass/compass-integration.scss b/tests/shared-fixtures/compass/compass-integration.scss new file mode 100644 index 0000000..a7201d9 --- /dev/null +++ b/tests/shared-fixtures/compass/compass-integration.scss @@ -0,0 +1 @@ +@import "compass/reset"; diff --git a/tests/shared-fixtures/compass/sass/layout.scss b/tests/shared-fixtures/compass/sass/layout.scss deleted file mode 100644 index 7a2f1da..0000000 --- a/tests/shared-fixtures/compass/sass/layout.scss +++ /dev/null @@ -1,181 +0,0 @@ -body { - height: 100%; - color: #333; - font-family: 'Lato', sans-serif; - font-size: 16px; - line-height: 1.42857; - /*background: linear-gradient(135deg, #99aaa0 0%,#d197b3 33%,#e5c2c4 65%,#76588c 100%) center center / cover fixed; !* W3C *!*/ - /*background: linear-gradient(135deg, rgba(15,11,11,1) 0%,rgba(234,132,7,1) 100%) center center / cover fixed; !* W3C *!*/ - /*background: linear-gradient(135deg, rgba(173,28,52,1) 0%, rgba(237,211,220,1) 100%) center center / cover fixed; !* W3C *! */ - /*background: linear-gradient(to bottom, rgba(135,224,253,1) 0%,rgba(83,203,241,1) 40%,rgba(5,171,224,1) 100%) center center / cover fixed;*/ - - background: linear-gradient(to bottom, rgb(32, 23, 99) 0%, rgb(31, 19, 95) 40%, #a94442 100%) center center / cover fixed; - - & > .container { - width: 100%; - height: 100%; - padding: 0; - margin: 0; - } -} - -.page-sidebar, -.page-content { - transition: all 0.5s ease; - color: #fff; -} - -.page-sidebar { - position: fixed; - height: 100%; - left: 0; - z-index: 1000; - overflow-y: auto; - /*background: linear-gradient(to bottom, #b26cab 0%,#765c8b 100%); !* W3C *!*/ - background: linear-gradient(to bottom, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.5) 50%, rgba(255, 255, 255, 0) 100%); - border-right: 1px solid #fff; -} - -.page-sidebar, -.sidebar-nav > li { - width: 250px; -} - -.page-sidebar.toggled { - width: 0; -} - -.page-content { - padding-left: 275px; - - &.toggled { - padding-left: 25px; - - .toggle-btn { - opacity: 1; - } - } - &:not(.toggled) .toggle-btn { - opacity: 0; - } -} - -.toggle-btn { - cursor: pointer; -} - -.page-header { - font-size: 24px; - margin: 0; - padding: 40px 0 0 20px; - - &, - & > span { - line-height: 32px; - height: 75px; - } - -} - -.sidebar-nav { - list-style-type: none; - padding: 0; - margin: 0; - - & > li { - &.sidebar-brand span { - float: right; - padding-right: 10px; - } - - &:not(.sidebar-brand) { - padding: 10px 15px; - font-size: 16px; - text-align: right; - text-transform: uppercase; - background: transparent; - clear: both; - } - } - - & > li.selected, - & > li:not(.sidebar-brand):not(.no-hover):hover { - background: #fff; - color: #000; - } - & > li:not(.sidebar-brand):not(.selected):hover { - cursor: pointer; - } -} - -.page-content:not(.toggled) .toggle-btn { - opacity: 0; -} - -.page-loading { - background: rgba(2, 2, 2, 0.5); - z-index: 10002; - width: 100%; - height: 100%; - position: absolute; - - & > .loading-animation { - margin: auto; - top: 50%; - zoom: 4; - } -} - -.no-scroll-mode { - position: fixed; -} - -#notification-area { - width: calc(100% - 50px); - border: 1px solid; - min-height: 100px; - position: absolute; - border-radius: 10px; - - font-size: 72px; - z-index: 1; - text-align: center; - font-style: italic; -} - -#notification-area > .notification-control { - float: right; - margin: 10px; - font-size: 24px; - - &:hover { - font-weight: bolder; - cursor: pointer; - } -} - -#modal-area { - color: #000; - h4, label { - text-transform: uppercase; - } - - .help-block { - font-size: 14px; - font-style: italic; - } -} - -.container-fluid { - padding-left: 0; - padding-right: 0; - padding-bottom: 25px; -} - -.pagination-area { - text-align: center; -} - -a.history-title { - color: #F5DEB3; -} diff --git a/tests/shared-fixtures/composer.phpunit.json b/tests/shared-fixtures/composer.phpunit.json deleted file mode 100644 index e7e11de..0000000 --- a/tests/shared-fixtures/composer.phpunit.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "eugene-matvejev/css-compiler", - "description": "compiles SASS and LESS assets on composer's callback", - "type": "lib", - "license": "MIT", - "authors": [ - { - "name": "Eugene Matvejev", - "email": "eugene.matvejev@gmail.com" - } - ], - "autoload": { - "psr-4": { - "EM\\CssCompiler\\": "src/" - }, - "classmap": [ - "ScriptHandler.php" - ] - }, - "autoload-dev": { - "psr-4": { - "EM\\Tests\\CssCompiler\\": "tests/" - } - }, - "require": { - "php": ">= 5.6", - "leafo/lessphp": "^0.5", - "leafo/scssphp": "@dev", - "leafo/scssphp-compass": "@dev" - }, - "require-dev": { - "composer/composer": "^1.1", - "phpunit/phpunit": "^5.3" - } -} diff --git a/tests/shared-fixtures/integration/app.scss b/tests/shared-fixtures/integration/app.scss new file mode 100644 index 0000000..e61843c --- /dev/null +++ b/tests/shared-fixtures/integration/app.scss @@ -0,0 +1,5 @@ +@import "../compass/compass-integration"; +@import "../scss/layout"; +@import "../scss/layout-loading-animation"; +@import "../scss/game"; +@import "../scss/game-results"; diff --git a/tests/shared-fixtures/less/print.less b/tests/shared-fixtures/less/print.less new file mode 100644 index 0000000..0401cd7 --- /dev/null +++ b/tests/shared-fixtures/less/print.less @@ -0,0 +1,53 @@ +@media print { + *, + *:before, + *:after { + background: transparent !important; + color: #000 !important; // Black prints faster: h5bp.com/s + box-shadow: none !important; + text-shadow: none !important; + } + + a, + a:visited { + text-decoration: underline; + } + + a[href]:after { + content: " (" attr(href) ")"; + } + + abbr[title]:after { + content: " (" attr(title) ")"; + } + + // Don't show links that are fragment identifiers, + // or use the `javascript:` pseudo protocol + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + + thead { + display: table-header-group; // h5bp.com/t + } + + // Bootstrap specific changes start + + // Bootstrap components + .navbar { + display: none; + } + .btn, + .dropup > .btn { + > .caret { + border-top-color: #000 !important; + } + } +} diff --git a/tests/shared-fixtures/not-valid-less/print.less b/tests/shared-fixtures/not-valid-less/print.less new file mode 100644 index 0000000..40cf155 --- /dev/null +++ b/tests/shared-fixtures/not-valid-less/print.less @@ -0,0 +1,32 @@ +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ + +// ========================================================================== +// Print styles. +// Inlined to avoid the additional HTTP request: h5bp.com/r +// ========================================================================== + +@media print + + //*, + 12, // ERROR + *:before, + *:after { + background: transparent !important; + color: #000 !important; // Black prints faster: h5bp.com/s + box-shadow: none !important; + text-shadow: none !important; + } + + a, + a:visited { + text-decoration: underline; + } + + a[href]:after { + content: " (" attr(href) ")"; + } + + abbr[title]:after { + content: " (" attr(title) ")"; + } +} diff --git a/tests/shared-fixtures/not-valid-scss/game-results.scss b/tests/shared-fixtures/not-valid-scss/game-results.scss new file mode 100644 index 0000000..6ccd7da --- /dev/null +++ b/tests/shared-fixtures/not-valid-scss/game-results.scss @@ -0,0 +1,3 @@ +div#game-results-area table.table { + margin 20px 0; // ERROR +} diff --git a/tests/shared-fixtures/scss/game.scss b/tests/shared-fixtures/scss/game/game.scss similarity index 99% rename from tests/shared-fixtures/scss/game.scss rename to tests/shared-fixtures/scss/game/game.scss index ed94cfe..45e2cec 100644 --- a/tests/shared-fixtures/scss/game.scss +++ b/tests/shared-fixtures/scss/game/game.scss @@ -1,4 +1,3 @@ - .battlefield-cell-container { display: flex; diff --git a/tests/shared-fixtures/scss/game-results.scss b/tests/shared-fixtures/scss/game/results/game-results.scss similarity index 100% rename from tests/shared-fixtures/scss/game-results.scss rename to tests/shared-fixtures/scss/game/results/game-results.scss diff --git a/tests/shared-fixtures/sass/layout-loading-animation.scss b/tests/shared-fixtures/scss/layout-loading-animation.scss similarity index 100% rename from tests/shared-fixtures/sass/layout-loading-animation.scss rename to tests/shared-fixtures/scss/layout-loading-animation.scss diff --git a/tests/shared-fixtures/sass/layout.scss b/tests/shared-fixtures/scss/layout.scss similarity index 51% rename from tests/shared-fixtures/sass/layout.scss rename to tests/shared-fixtures/scss/layout.scss index 7a2f1da..ca16409 100644 --- a/tests/shared-fixtures/sass/layout.scss +++ b/tests/shared-fixtures/scss/layout.scss @@ -19,23 +19,6 @@ body { } } -.page-sidebar, -.page-content { - transition: all 0.5s ease; - color: #fff; -} - -.page-sidebar { - position: fixed; - height: 100%; - left: 0; - z-index: 1000; - overflow-y: auto; - /*background: linear-gradient(to bottom, #b26cab 0%,#765c8b 100%); !* W3C *!*/ - background: linear-gradient(to bottom, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.5) 50%, rgba(255, 255, 255, 0) 100%); - border-right: 1px solid #fff; -} - .page-sidebar, .sidebar-nav > li { width: 250px; @@ -60,23 +43,6 @@ body { } } -.toggle-btn { - cursor: pointer; -} - -.page-header { - font-size: 24px; - margin: 0; - padding: 40px 0 0 20px; - - &, - & > span { - line-height: 32px; - height: 75px; - } - -} - .sidebar-nav { list-style-type: none; padding: 0; @@ -107,75 +73,3 @@ body { cursor: pointer; } } - -.page-content:not(.toggled) .toggle-btn { - opacity: 0; -} - -.page-loading { - background: rgba(2, 2, 2, 0.5); - z-index: 10002; - width: 100%; - height: 100%; - position: absolute; - - & > .loading-animation { - margin: auto; - top: 50%; - zoom: 4; - } -} - -.no-scroll-mode { - position: fixed; -} - -#notification-area { - width: calc(100% - 50px); - border: 1px solid; - min-height: 100px; - position: absolute; - border-radius: 10px; - - font-size: 72px; - z-index: 1; - text-align: center; - font-style: italic; -} - -#notification-area > .notification-control { - float: right; - margin: 10px; - font-size: 24px; - - &:hover { - font-weight: bolder; - cursor: pointer; - } -} - -#modal-area { - color: #000; - h4, label { - text-transform: uppercase; - } - - .help-block { - font-size: 14px; - font-style: italic; - } -} - -.container-fluid { - padding-left: 0; - padding-right: 0; - padding-bottom: 25px; -} - -.pagination-area { - text-align: center; -} - -a.history-title { - color: #F5DEB3; -}