diff --git a/.gitignore b/.gitignore index 4e78ae3..db11d2e 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,29 @@ run-tests.log /test/*/*/*/*.log /test/*/*/*/*.out + +# Added by horde-components QC --fix-qc-issues +# Build artifacts directory +/build/ +# Composer dependencies directory +/vendor/ +# Composer lock file (libraries should not commit lock files) +/composer.lock +# PHPStorm IDE settings +/.idea/ +# VSCode IDE settings +/.vscode/ +# Claude Code CLI cache and state +/.claude/ +# Cline extension data +/.cline/ +# PHP CS Fixer cache file +/.php-cs-fixer.cache +# PHPUnit result cache +/.phpunit.result.cache +# PHPUnit Cache (other) +/.phpunit.cache +# PHPStan local configuration +/phpstan.neon +# PHPStan cache directory +/.phpstan.cache/ diff --git a/.horde.yml b/.horde.yml index 80b8827..60bc479 100644 --- a/.horde.yml +++ b/.horde.yml @@ -23,8 +23,12 @@ license: uri: http://www.horde.org/licenses/lgpl21 dependencies: required: - php: ^7.4 || ^8 + php: ^8.1 composer: horde/exception: ^3 horde/log: ^3 horde/util: ^3 +keywords: + - wrapper + - minifier +vendor: horde diff --git a/composer.json b/composer.json index c2dc352..43f2bf6 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,8 @@ }, "autoload-dev": { "psr-4": { - "Horde\\JavascriptMinify\\Test\\": "test/" + "Horde\\JavascriptMinify\\Test\\": "test/unit/", + "Horde\\JavascriptMinify\\Test\\Integration\\": "test/integration/" } }, "config": { diff --git a/lib/Horde/JavascriptMinify.php b/lib/Horde/JavascriptMinify.php index 773430d..a299586 100644 --- a/lib/Horde/JavascriptMinify.php +++ b/lib/Horde/JavascriptMinify.php @@ -1,6 +1,7 @@ */ - public function setOptions(array $opts = array()) + public function setOptions(array $opts = []) { $this->_opts = array_merge($this->_opts, $opts); // Ensure we have a logger object. - if (!isset($this->_opts['logger']) || - !($this->_opts['logger'] instanceof Horde_Log_Logger)) { + if (!isset($this->_opts['logger']) + || !($this->_opts['logger'] instanceof Horde_Log_Logger)) { $this->_opts['logger'] = new Horde_Log_Logger( new Horde_Log_Handler_Null() ); @@ -108,9 +109,9 @@ abstract public function minify(); */ public function sourcemap() { - if (is_null($this->_sourcemap) || - !is_readable($this->_sourcemap) || - !strlen($sourcemap = file_get_contents($this->_sourcemap))) { + if (is_null($this->_sourcemap) + || !is_readable($this->_sourcemap) + || !strlen($sourcemap = file_get_contents($this->_sourcemap))) { return null; } @@ -118,7 +119,7 @@ public function sourcemap() * contains filenames, and convert to URLs. */ $sourcemap = json_decode($sourcemap); $data_lookup = array_flip($this->_data); - $new_sources = array(); + $new_sources = []; foreach ($sourcemap->sources as $val) { $new_sources[] = $data_lookup[$val]; diff --git a/lib/Horde/JavascriptMinify/Closure.php b/lib/Horde/JavascriptMinify/Closure.php index 93fc8db..e1c6aa0 100644 --- a/lib/Horde/JavascriptMinify/Closure.php +++ b/lib/Horde/JavascriptMinify/Closure.php @@ -1,6 +1,7 @@ */ - public function setOptions(array $opts = array()) + public function setOptions(array $opts = []) { - foreach (array('closure', 'java') as $val) { + foreach (['closure', 'java'] as $val) { if (!isset($opts[$val])) { throw new InvalidArgumentException( sprintf('Missing required %s option.', $val) @@ -49,8 +50,8 @@ public function setOptions(array $opts = array()) */ public function minify() { - if (!is_executable($this->_opts['java']) || - !is_readable($this->_opts['closure'])) { + if (!is_executable($this->_opts['java']) + || !is_readable($this->_opts['closure'])) { $this->_opts['logger']->log( 'The java path or Closure location can not be accessed.', Horde_Log::ERR @@ -64,9 +65,9 @@ public function minify() $cmd = trim(escapeshellcmd($this->_opts['java']) . ' -jar ' . escapeshellarg($this->_opts['closure']) . ' --warning_level QUIET'); if (isset($this->_opts['sourcemap']) && is_array($this->_data)) { $this->_sourcemap = Horde_Util::getTempFile(); - $cmd .= ' --create_source_map ' . - escapeshellarg($this->_sourcemap) . - ' --source_map_format=V3'; + $cmd .= ' --create_source_map ' + . escapeshellarg($this->_sourcemap) + . ' --source_map_format=V3'; $suffix = "\n//# sourceMappingURL=" . $this->_opts['sourcemap']; } else { $suffix = ''; diff --git a/lib/Horde/JavascriptMinify/Exception.php b/lib/Horde/JavascriptMinify/Exception.php index 1a32138..d4b6a31 100644 --- a/lib/Horde/JavascriptMinify/Exception.php +++ b/lib/Horde/JavascriptMinify/Exception.php @@ -1,6 +1,7 @@ */ - public function setOptions(array $opts = array()) + public function setOptions(array $opts = []) { if (!isset($opts['uglifyjs'])) { throw new InvalidArgumentException('Missing required uglifyjs option.'); @@ -58,10 +59,10 @@ public function minify() /* Sourcemaps only supported by UglifyJS2. */ if (isset($this->_opts['sourcemap']) && is_array($this->_data)) { $this->_sourcemap = Horde_Util::getTempFile(); - $cmd .= ' --source-map ' . - escapeshellarg($this->_sourcemap) . - ' --source-map-url ' . - escapeshellarg($this->_opts['sourcemap']); + $cmd .= ' --source-map ' + . escapeshellarg($this->_sourcemap) + . ' --source-map-url ' + . escapeshellarg($this->_opts['sourcemap']); } if (isset($this->_opts['cmdline'])) { $cmd .= ' ' . $this->_opts['cmdline']; diff --git a/lib/Horde/JavascriptMinify/Util/Cmdline.php b/lib/Horde/JavascriptMinify/Util/Cmdline.php index 4043c3f..1e2f34c 100644 --- a/lib/Horde/JavascriptMinify/Util/Cmdline.php +++ b/lib/Horde/JavascriptMinify/Util/Cmdline.php @@ -1,6 +1,7 @@ array('pipe', 'r'), - 1 => array('pipe', 'w'), - 2 => array('pipe', 'w') - ); + $descspec = [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; $process = proc_open($cmd, $descspec, $pipes); diff --git a/lib/Horde/JavascriptMinify/Yui.php b/lib/Horde/JavascriptMinify/Yui.php index a1fbbe8..d8c3b4c 100644 --- a/lib/Horde/JavascriptMinify/Yui.php +++ b/lib/Horde/JavascriptMinify/Yui.php @@ -1,6 +1,7 @@ */ - public function setOptions(array $opts = array()) + public function setOptions(array $opts = []) { - foreach (array('java', 'yui') as $val) { + foreach (['java', 'yui'] as $val) { if (!isset($opts[$val])) { throw new InvalidArgumentException( sprintf('Missing required %s option.', $val) @@ -49,8 +50,8 @@ public function minify() { $js = parent::minify(); - if (!is_executable($this->_opts['java']) || - !is_readable($this->_opts['yui'])) { + if (!is_executable($this->_opts['java']) + || !is_readable($this->_opts['yui'])) { $this->_opts['logger']->log( 'The java path or YUI location can not be accessed.', Horde_Log::ERR @@ -64,8 +65,8 @@ public function minify() } $cmdline = new Horde_JavascriptMinify_Util_Cmdline(); - return $cmdline->runCmd($js, trim($cmd), $this->_opts['logger']) . - $this->_sourceUrls(); + return $cmdline->runCmd($js, trim($cmd), $this->_opts['logger']) + . $this->_sourceUrls(); } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index e17df7e..d1ffca2 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,13 +1,28 @@ - - - - - lib - - - - - test - - + + + + + test/unit + + + test/integration + + + + + lib + + diff --git a/test/Horde/JavascriptMinify/AllTests.php b/test/Horde/JavascriptMinify/AllTests.php deleted file mode 100644 index 79e2099..0000000 --- a/test/Horde/JavascriptMinify/AllTests.php +++ /dev/null @@ -1,5 +0,0 @@ -run(); diff --git a/test/Horde/JavascriptMinify/ClosureTest.php b/test/Horde/JavascriptMinify/ClosureTest.php deleted file mode 100644 index 184ede3..0000000 --- a/test/Horde/JavascriptMinify/ClosureTest.php +++ /dev/null @@ -1,67 +0,0 @@ - - * @category Horde - * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 - * @package JavascriptMinify - * @subpackage UnitTests - */ -namespace Horde\JavascriptMinify; - -/** - * Tests the Closure backend. - * - * @author Jan Schneider - * @category Horde - * @copyright 2017 Horde LLC - * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 - * @package JavascriptMinify - * @subpackage UnitTests - */ -class ClosureTest extends TestBase -{ - protected $_config; - - public function setUp(): void - { - $this->_config = self::getConfig( - 'JAVASCRIPTMINIFY_CLOSURE_TEST_CONFIG', - __DIR__ - ); - if (!$this->_config || - empty($this->_config['javascriptminify']['closure'])) { - $this->markTestSkipped('Closure compressor not configured'); - } - } - - public function testMinify() - { - $this->_minify(); - } - - public function testSourcemap() - { - $this->_sourcemap(); - } - - public function testToString() - { - $this->_toString(); - } - - protected function _getMinifier($files = false) - { - $opts = $this->_config['javascriptminify']['closure']; - if ($files) { - $opts['sourcemap'] = 'https://www.example.com/js/sourcemap'; - } - return new Horde_JavascriptMinify_Closure( - $this->_getFixture($files), $opts - ); - } -} diff --git a/test/Horde/JavascriptMinify/NullTest.php b/test/Horde/JavascriptMinify/NullTest.php deleted file mode 100644 index 83cd188..0000000 --- a/test/Horde/JavascriptMinify/NullTest.php +++ /dev/null @@ -1,38 +0,0 @@ - - * @category Horde - * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 - * @package JavascriptMinify - * @subpackage UnitTests - */ -namespace Horde\JavascriptMinify; -use \Horde_JavascriptMinify_Null; - -/** - * Tests the Null backend. - * - * @author Jan Schneider - * @category Horde - * @copyright 2017 Horde LLC - * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 - * @package JavascriptMinify - * @subpackage UnitTests - */ -class NullTest extends TestBase -{ - public function testToString() - { - $this->_toString(); - } - - protected function _getMinifier() - { - return new Horde_JavascriptMinify_Null($this->_getFixture()); - } -} diff --git a/test/Horde/JavascriptMinify/TestBase.php b/test/Horde/JavascriptMinify/TestBase.php deleted file mode 100644 index 56c7859..0000000 --- a/test/Horde/JavascriptMinify/TestBase.php +++ /dev/null @@ -1,102 +0,0 @@ - - * @category Horde - * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 - * @package JavascriptMinify - * @subpackage UnitTests - */ -namespace Horde\JavascriptMinify; -use Horde_Test_Case as TestCase; - -/** - * Base class for backend unit tests. - * - * @author Jan Schneider - * @category Horde - * @copyright 2017 Horde LLC - * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 - * @package JavascriptMinify - * @subpackage UnitTests - */ -abstract class TestBase extends TestCase -{ - public function setUp(): void - { - } - - protected function _minify() - { - $minifier = $this->_getMinifier(); - $minified = $minifier->minify(); - $this->assertNotEmpty($minified); - $this->assertLessThan( - strlen($this->_getFixture()), - strlen($minified) - ); - - $minifier = $this->_getMinifier(true); - $minified = $minifier->minify(); - $this->assertNotEmpty($minified); - $this->assertLessThan( - strlen(implode('', array_map('file_get_contents', $this->_getFixture(true)))), - strlen($minified) - ); - } - - protected function _sourcemap() - { - $minifier = $this->_getMinifier(true, true); - $minifier->minify(); - $sourcemap = $minifier->sourcemap(); - $this->assertNotEmpty($sourcemap); - $this->assertNotEmpty(json_decode($sourcemap)); - } - - protected function _toString() - { - $minifier = $this->_getMinifier(); - $this->assertEquals($minifier->minify(), (string)$minifier); - } - - abstract protected function _getMinifier(); - - protected function _getFixture($files = false) - { - if ($files) { - return array( - 'https://www.example.com/js/one.js' => __DIR__ . '/fixtures/one.js', - 'https://www.example.com/js/two.js' => __DIR__ . '/fixtures/two.js', - ); - } - - return << - * @category Horde - * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 - * @package JavascriptMinify - * @subpackage UnitTests - */ -namespace Horde\JavascriptMinify; - -/** - * Tests the UglifyJS backend. - * - * @author Jan Schneider - * @category Horde - * @copyright 2017 Horde LLC - * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 - * @package JavascriptMinify - * @subpackage UnitTests - */ -class UglifyjsTest extends TestBase -{ - protected $_config; - - public function setUp(): void - { - $this->_config = self::getConfig( - 'JAVASCRIPTMINIFY_UGLIFYJS_TEST_CONFIG', - __DIR__ - ); - if (!$this->_config || - empty($this->_config['javascriptminify']['uglifyjs'])) { - $this->markTestSkipped('UglifyJS compressor not configured'); - } - } - - public function testMinify() - { - $this->_minify(); - } - - public function testSourcemap() - { - $this->_sourcemap(); - } - - public function testToString() - { - $this->_toString(); - } - - protected function _getMinifier($files = false) - { - $opts = $this->_config['javascriptminify']['uglifyjs']; - if ($files) { - $opts['sourcemap'] = 'https://www.example.com/js/sourcemap'; - } - return new Horde_JavascriptMinify_Uglifyjs( - $this->_getFixture($files), $opts - ); - } -} diff --git a/test/Horde/JavascriptMinify/YuiTest.php b/test/Horde/JavascriptMinify/YuiTest.php deleted file mode 100644 index e3f4c13..0000000 --- a/test/Horde/JavascriptMinify/YuiTest.php +++ /dev/null @@ -1,59 +0,0 @@ - - * @category Horde - * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 - * @package JavascriptMinify - * @subpackage UnitTests - */ -namespace Horde\JavascriptMinify; - -/** - * Tests the YUI backend. - * - * @author Jan Schneider - * @category Horde - * @copyright 2017 Horde LLC - * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 - * @package JavascriptMinify - * @subpackage UnitTests - */ -class YuiTest extends TestBase -{ - protected $_config; - - public function setUp(): void - { - $this->_config = self::getConfig( - 'JAVASCRIPTMINIFY_YUI_TEST_CONFIG', - __DIR__ - ); - if (!$this->_config || - empty($this->_config['javascriptminify']['yui'])) { - $this->markTestSkipped('YUI compressor not configured'); - } - } - - public function testMinify() - { - $this->_minify(); - } - - public function testToString() - { - $this->_toString(); - } - - protected function _getMinifier($files = false) - { - return new Horde_JavascriptMinify_Yui( - $this->_getFixture($files), - $this->_config['javascriptminify']['yui'] - ); - } -} diff --git a/test/Horde/JavascriptMinify/bootstrap.php b/test/Horde/JavascriptMinify/bootstrap.php deleted file mode 100644 index bcf408d..0000000 --- a/test/Horde/JavascriptMinify/bootstrap.php +++ /dev/null @@ -1,9 +0,0 @@ -loadConfig('JAVASCRIPTMINIFY_CLOSURE_TEST_CONFIG'); + if ($config === null || empty($config['javascriptminify']['closure'])) { + $this->markTestSkipped('Closure compressor not configured'); + } + $this->config = $config['javascriptminify']['closure']; + } + + public function testMinifyString(): void + { + $fixture = $this->getFixtureString(); + $minifier = new Horde_JavascriptMinify_Closure($fixture, $this->config); + + $this->assertMinifies($fixture, $minifier->minify()); + } + + public function testMinifyFiles(): void + { + $files = $this->getFixtureFiles(); + $minifier = new Horde_JavascriptMinify_Closure($files, $this->config); + $minified = $minifier->minify(); + $original = implode('', array_map('file_get_contents', $files)); + + $this->assertMinifies($original, $minified); + } + + public function testSourcemap(): void + { + $opts = array_merge($this->config, [ + 'sourcemap' => 'https://www.example.com/js/sourcemap', + ]); + $minifier = new Horde_JavascriptMinify_Closure( + $this->getFixtureFiles(), + $opts + ); + $minifier->minify(); + + $sourcemap = $minifier->sourcemap(); + $this->assertNotEmpty($sourcemap); + $this->assertNotEmpty(json_decode($sourcemap)); + } + + public function testToString(): void + { + $minifier = new Horde_JavascriptMinify_Closure( + $this->getFixtureString(), + $this->config + ); + + $this->assertSame($minifier->minify(), (string) $minifier); + } +} diff --git a/test/integration/IntegrationTestBase.php b/test/integration/IntegrationTestBase.php new file mode 100644 index 0000000..b117814 --- /dev/null +++ b/test/integration/IntegrationTestBase.php @@ -0,0 +1,66 @@ + $fixtureDir . '/one.js', + 'https://www.example.com/js/two.js' => $fixtureDir . '/two.js', + ]; + } + + protected function loadConfig(string $envVar): ?array + { + $configFile = getenv($envVar); + if ($configFile === false || !is_readable($configFile)) { + return null; + } + + return require $configFile; + } + + protected function assertMinifies(string $original, string $minified): void + { + $this->assertNotEmpty($minified); + $this->assertLessThan(strlen($original), strlen($minified)); + } +} diff --git a/test/integration/UglifyjsTest.php b/test/integration/UglifyjsTest.php new file mode 100644 index 0000000..d7f5253 --- /dev/null +++ b/test/integration/UglifyjsTest.php @@ -0,0 +1,74 @@ +loadConfig('JAVASCRIPTMINIFY_UGLIFYJS_TEST_CONFIG'); + if ($config === null || empty($config['javascriptminify']['uglifyjs'])) { + $this->markTestSkipped('UglifyJS compressor not configured'); + } + $this->config = $config['javascriptminify']['uglifyjs']; + } + + public function testMinifyString(): void + { + $fixture = $this->getFixtureString(); + $minifier = new Horde_JavascriptMinify_Uglifyjs($fixture, $this->config); + + $this->assertMinifies($fixture, $minifier->minify()); + } + + public function testMinifyFiles(): void + { + $files = $this->getFixtureFiles(); + $minifier = new Horde_JavascriptMinify_Uglifyjs($files, $this->config); + $minified = $minifier->minify(); + $original = implode('', array_map('file_get_contents', $files)); + + $this->assertMinifies($original, $minified); + } + + public function testSourcemap(): void + { + $opts = array_merge($this->config, [ + 'sourcemap' => 'https://www.example.com/js/sourcemap', + ]); + $minifier = new Horde_JavascriptMinify_Uglifyjs( + $this->getFixtureFiles(), + $opts + ); + $minifier->minify(); + + $sourcemap = $minifier->sourcemap(); + $this->assertNotEmpty($sourcemap); + $this->assertNotEmpty(json_decode($sourcemap)); + } + + public function testToString(): void + { + $minifier = new Horde_JavascriptMinify_Uglifyjs( + $this->getFixtureString(), + $this->config + ); + + $this->assertSame($minifier->minify(), (string) $minifier); + } +} diff --git a/test/integration/YuiTest.php b/test/integration/YuiTest.php new file mode 100644 index 0000000..eaca575 --- /dev/null +++ b/test/integration/YuiTest.php @@ -0,0 +1,58 @@ +loadConfig('JAVASCRIPTMINIFY_YUI_TEST_CONFIG'); + if ($config === null || empty($config['javascriptminify']['yui'])) { + $this->markTestSkipped('YUI compressor not configured'); + } + $this->config = $config['javascriptminify']['yui']; + } + + public function testMinifyString(): void + { + $fixture = $this->getFixtureString(); + $minifier = new Horde_JavascriptMinify_Yui($fixture, $this->config); + + $this->assertMinifies($fixture, $minifier->minify()); + } + + public function testMinifyFiles(): void + { + $files = $this->getFixtureFiles(); + $minifier = new Horde_JavascriptMinify_Yui($files, $this->config); + $minified = $minifier->minify(); + $original = implode('', array_map('file_get_contents', $files)); + + $this->assertMinifies($original, $minified); + } + + public function testToString(): void + { + $minifier = new Horde_JavascriptMinify_Yui( + $this->getFixtureString(), + $this->config + ); + + $this->assertSame($minifier->minify(), (string) $minifier); + } +} diff --git a/test/unit/ClosureSetOptionsTest.php b/test/unit/ClosureSetOptionsTest.php new file mode 100644 index 0000000..dcd7d18 --- /dev/null +++ b/test/unit/ClosureSetOptionsTest.php @@ -0,0 +1,73 @@ +expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('java'); + + new Horde_JavascriptMinify_Closure('var x = 1;', [ + 'closure' => '/usr/bin/closure', + ]); + } + + public function testSetOptionsRequiresClosureOption(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('closure'); + + new Horde_JavascriptMinify_Closure('var x = 1;', [ + 'java' => '/usr/bin/java', + ]); + } + + public function testSetOptionsAcceptsValidOptions(): void + { + $minifier = new Horde_JavascriptMinify_Closure('var x = 1;', [ + 'java' => '/usr/bin/java', + 'closure' => '/usr/bin/closure', + ]); + + $this->assertInstanceOf(Horde_JavascriptMinify_Closure::class, $minifier); + } + + public function testSetOptionsAcceptsOptionalCmdline(): void + { + $minifier = new Horde_JavascriptMinify_Closure('var x = 1;', [ + 'java' => '/usr/bin/java', + 'closure' => '/usr/bin/closure', + 'cmdline' => '--compilation_level ADVANCED', + ]); + + $this->assertInstanceOf(Horde_JavascriptMinify_Closure::class, $minifier); + } + + public function testSetOptionsAcceptsOptionalSourcemap(): void + { + $minifier = new Horde_JavascriptMinify_Closure('var x = 1;', [ + 'java' => '/usr/bin/java', + 'closure' => '/usr/bin/closure', + 'sourcemap' => 'https://example.com/sourcemap', + ]); + + $this->assertInstanceOf(Horde_JavascriptMinify_Closure::class, $minifier); + } +} diff --git a/test/unit/CmdlineTest.php b/test/unit/CmdlineTest.php new file mode 100644 index 0000000..5f07abc --- /dev/null +++ b/test/unit/CmdlineTest.php @@ -0,0 +1,81 @@ +runCmd($input, 'cat', $this->createNullLogger()); + + $this->assertSame($input, $result); + } + + public function testRunCmdReturnsEmptyStringForEmptyInput(): void + { + $cmdline = new Horde_JavascriptMinify_Util_Cmdline(); + + $result = $cmdline->runCmd('', 'cat', $this->createNullLogger()); + + $this->assertSame('', $result); + } + + public function testRunCmdPassesLargeInput(): void + { + $cmdline = new Horde_JavascriptMinify_Util_Cmdline(); + + $input = str_repeat('var x = 1;', 1000); + $result = $cmdline->runCmd($input, 'cat', $this->createNullLogger()); + + $this->assertSame($input, $result); + } + + /** + * The source code uses the string 'WARN' as a log level, which + * Horde_Log_Logger does not recognize. This test documents the + * current (broken) behavior: stderr output causes a Horde_Log_Exception. + */ + public function testRunCmdWithStderrThrowsLogException(): void + { + $cmdline = new Horde_JavascriptMinify_Util_Cmdline(); + + $this->expectException(Horde_Log_Exception::class); + $this->expectExceptionMessage('Bad log level'); + + $cmdline->runCmd('', 'echo "test error" >&2', $this->createNullLogger()); + } + + public function testRunCmdExecutesCommandTransformation(): void + { + $cmdline = new Horde_JavascriptMinify_Util_Cmdline(); + + $result = $cmdline->runCmd('hello', 'tr "a-z" "A-Z"', $this->createNullLogger()); + + $this->assertSame('HELLO', $result); + } +} diff --git a/test/unit/ConstructorTest.php b/test/unit/ConstructorTest.php new file mode 100644 index 0000000..9f7d50d --- /dev/null +++ b/test/unit/ConstructorTest.php @@ -0,0 +1,119 @@ +assertSame($js, $data->getValue($minifier)); + } + + public function testConstructorAcceptsArray(): void + { + $files = [ + 'https://example.com/one.js' => '/tmp/one.js', + ]; + $minifier = new Horde_JavascriptMinify_Null($files); + + $data = new ReflectionProperty(Horde_JavascriptMinify::class, '_data'); + $this->assertSame($files, $data->getValue($minifier)); + } + + public function testConstructorRejectsInteger(): void + { + $this->expectException(InvalidArgumentException::class); + new Horde_JavascriptMinify_Null(42); + } + + public function testConstructorRejectsNull(): void + { + $this->expectException(InvalidArgumentException::class); + new Horde_JavascriptMinify_Null(null); + } + + public function testConstructorRejectsObject(): void + { + $this->expectException(InvalidArgumentException::class); + new Horde_JavascriptMinify_Null(new stdClass()); + } + + public function testSetOptionsCreatesNullLoggerByDefault(): void + { + $minifier = new Horde_JavascriptMinify_Null('var x = 1;'); + + $opts = new ReflectionProperty(Horde_JavascriptMinify::class, '_opts'); + $optsValue = $opts->getValue($minifier); + + $this->assertInstanceOf(Horde_Log_Logger::class, $optsValue['logger']); + } + + public function testSetOptionsAcceptsCustomLogger(): void + { + $logger = new Horde_Log_Logger(new Horde_Log_Handler_Null()); + $minifier = new Horde_JavascriptMinify_Null('var x = 1;', ['logger' => $logger]); + + $opts = new ReflectionProperty(Horde_JavascriptMinify::class, '_opts'); + $optsValue = $opts->getValue($minifier); + + $this->assertSame($logger, $optsValue['logger']); + } + + public function testSetOptionsMergesOptions(): void + { + $minifier = new Horde_JavascriptMinify_Null('var x = 1;', ['foo' => 'bar']); + $minifier->setOptions(['baz' => 'qux']); + + $opts = new ReflectionProperty(Horde_JavascriptMinify::class, '_opts'); + $optsValue = $opts->getValue($minifier); + + $this->assertSame('bar', $optsValue['foo']); + $this->assertSame('qux', $optsValue['baz']); + } + + public function testSetOptionsOverridesExistingKeys(): void + { + $minifier = new Horde_JavascriptMinify_Null('var x = 1;', ['foo' => 'bar']); + $minifier->setOptions(['foo' => 'updated']); + + $opts = new ReflectionProperty(Horde_JavascriptMinify::class, '_opts'); + $optsValue = $opts->getValue($minifier); + + $this->assertSame('updated', $optsValue['foo']); + } + + public function testConstructorPassesOptsToSetOptions(): void + { + $minifier = new Horde_JavascriptMinify_Null('var x = 1;', ['custom' => 'value']); + + $opts = new ReflectionProperty(Horde_JavascriptMinify::class, '_opts'); + $optsValue = $opts->getValue($minifier); + + $this->assertSame('value', $optsValue['custom']); + } +} diff --git a/test/unit/NullTest.php b/test/unit/NullTest.php new file mode 100644 index 0000000..4f0b386 --- /dev/null +++ b/test/unit/NullTest.php @@ -0,0 +1,99 @@ +fixtureDir = dirname(__DIR__) . '/fixtures'; + } + + public function testMinifyWithStringReturnsUnmodifiedString(): void + { + $js = "var x = 1;\nvar y = 2;"; + $minifier = new Horde_JavascriptMinify_Null($js); + + $this->assertSame($js, $minifier->minify()); + } + + public function testMinifyWithEmptyStringReturnsEmptyString(): void + { + $minifier = new Horde_JavascriptMinify_Null(''); + + $this->assertSame('', $minifier->minify()); + } + + public function testMinifyWithFilesReturnsConcatenatedContents(): void + { + $files = [ + 'https://example.com/one.js' => $this->fixtureDir . '/one.js', + 'https://example.com/two.js' => $this->fixtureDir . '/two.js', + ]; + $minifier = new Horde_JavascriptMinify_Null($files); + $result = $minifier->minify(); + + $expected = file_get_contents($this->fixtureDir . '/one.js') . "\n" + . file_get_contents($this->fixtureDir . '/two.js') . "\n"; + + $this->assertSame($expected, $result); + } + + public function testMinifyWithSingleFileReturnsFileContentsWithNewline(): void + { + $files = [ + 'https://example.com/one.js' => $this->fixtureDir . '/one.js', + ]; + $minifier = new Horde_JavascriptMinify_Null($files); + $result = $minifier->minify(); + + $expected = file_get_contents($this->fixtureDir . '/one.js') . "\n"; + $this->assertSame($expected, $result); + } + + public function testMinifyWithUnreadableFileThrowsException(): void + { + $files = [ + 'https://example.com/missing.js' => '/nonexistent/path/missing.js', + ]; + $minifier = new Horde_JavascriptMinify_Null($files); + + $this->expectException(Horde_JavascriptMinify_Exception::class); + $minifier->minify(); + } + + public function testToStringReturnsMinifyResult(): void + { + $js = 'var x = 1;'; + $minifier = new Horde_JavascriptMinify_Null($js); + + $this->assertSame($minifier->minify(), (string) $minifier); + } + + public function testToStringWithFilesReturnsMinifyResult(): void + { + $files = [ + 'https://example.com/one.js' => $this->fixtureDir . '/one.js', + ]; + $minifier = new Horde_JavascriptMinify_Null($files); + + $this->assertSame($minifier->minify(), (string) $minifier); + } +} diff --git a/test/unit/SourceUrlsTest.php b/test/unit/SourceUrlsTest.php new file mode 100644 index 0000000..094567d --- /dev/null +++ b/test/unit/SourceUrlsTest.php @@ -0,0 +1,75 @@ +_sourceUrls(); + } + }; + + $this->assertSame('', $minifier->getSourceUrls()); + } + + public function testSourceUrlsListedForFileInput(): void + { + $files = [ + 'https://example.com/js/one.js' => dirname(__DIR__) . '/fixtures/one.js', + 'https://example.com/js/two.js' => dirname(__DIR__) . '/fixtures/two.js', + ]; + + $minifier = new class ($files) extends Horde_JavascriptMinify_Null { + public function getSourceUrls(): string + { + return $this->_sourceUrls(); + } + }; + + $result = $minifier->getSourceUrls(); + + $this->assertStringContainsString('// @source: https://example.com/js/one.js', $result); + $this->assertStringContainsString('// @source: https://example.com/js/two.js', $result); + } + + public function testSourceUrlsFormatPerUrl(): void + { + $files = [ + 'https://example.com/app.js' => dirname(__DIR__) . '/fixtures/one.js', + ]; + + $minifier = new class ($files) extends Horde_JavascriptMinify_Null { + public function getSourceUrls(): string + { + return $this->_sourceUrls(); + } + }; + + $this->assertSame( + "\n// @source: https://example.com/app.js", + $minifier->getSourceUrls() + ); + } +} diff --git a/test/unit/SourcemapTest.php b/test/unit/SourcemapTest.php new file mode 100644 index 0000000..6bffd0c --- /dev/null +++ b/test/unit/SourcemapTest.php @@ -0,0 +1,102 @@ +minify(); + + $this->assertNull($minifier->sourcemap()); + } + + public function testSourcemapReturnsNullWhenFileNotReadable(): void + { + $files = [ + 'https://example.com/one.js' => dirname(__DIR__) . '/fixtures/one.js', + ]; + $minifier = new Horde_JavascriptMinify_Null($files); + $minifier->minify(); + + $prop = new ReflectionProperty(Horde_JavascriptMinify::class, '_sourcemap'); + $prop->setValue($minifier, '/nonexistent/sourcemap.json'); + + $this->assertNull($minifier->sourcemap()); + } + + public function testSourcemapReturnsNullWhenFileIsEmpty(): void + { + $files = [ + 'https://example.com/one.js' => dirname(__DIR__) . '/fixtures/one.js', + ]; + $minifier = new Horde_JavascriptMinify_Null($files); + $minifier->minify(); + + $tmpFile = tempnam(sys_get_temp_dir(), 'srcmap'); + file_put_contents($tmpFile, ''); + + $prop = new ReflectionProperty(Horde_JavascriptMinify::class, '_sourcemap'); + $prop->setValue($minifier, $tmpFile); + + $this->assertNull($minifier->sourcemap()); + unlink($tmpFile); + } + + public function testSourcemapParsesValidSourcemapFile(): void + { + $fixtureDir = dirname(__DIR__) . '/fixtures'; + $files = [ + 'https://example.com/js/one.js' => $fixtureDir . '/one.js', + 'https://example.com/js/two.js' => $fixtureDir . '/two.js', + ]; + $minifier = new Horde_JavascriptMinify_Null($files); + $minifier->minify(); + + $sourcemapData = json_encode([ + 'version' => 3, + 'sources' => [$fixtureDir . '/one.js', $fixtureDir . '/two.js'], + 'mappings' => 'AAAA', + 'sourceRoot' => '/root', + 'file' => 'output.js', + ]); + + $tmpFile = tempnam(sys_get_temp_dir(), 'srcmap'); + file_put_contents($tmpFile, $sourcemapData); + + $prop = new ReflectionProperty(Horde_JavascriptMinify::class, '_sourcemap'); + $prop->setValue($minifier, $tmpFile); + + $result = $minifier->sourcemap(); + $this->assertNotNull($result); + + $decoded = json_decode($result, true); + $this->assertSame(3, $decoded['version']); + $this->assertSame( + ['https://example.com/js/one.js', 'https://example.com/js/two.js'], + $decoded['sources'] + ); + $this->assertSame('AAAA', $decoded['mappings']); + $this->assertArrayNotHasKey('sourceRoot', $decoded); + $this->assertArrayNotHasKey('file', $decoded); + + unlink($tmpFile); + } +} diff --git a/test/unit/UglifyjsSetOptionsTest.php b/test/unit/UglifyjsSetOptionsTest.php new file mode 100644 index 0000000..3490cf8 --- /dev/null +++ b/test/unit/UglifyjsSetOptionsTest.php @@ -0,0 +1,58 @@ +expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('uglifyjs'); + + new Horde_JavascriptMinify_Uglifyjs('var x = 1;'); + } + + public function testSetOptionsAcceptsValidOptions(): void + { + $minifier = new Horde_JavascriptMinify_Uglifyjs('var x = 1;', [ + 'uglifyjs' => '/usr/bin/uglifyjs', + ]); + + $this->assertInstanceOf(Horde_JavascriptMinify_Uglifyjs::class, $minifier); + } + + public function testSetOptionsAcceptsOptionalCmdline(): void + { + $minifier = new Horde_JavascriptMinify_Uglifyjs('var x = 1;', [ + 'uglifyjs' => '/usr/bin/uglifyjs', + 'cmdline' => '--compress --mangle', + ]); + + $this->assertInstanceOf(Horde_JavascriptMinify_Uglifyjs::class, $minifier); + } + + public function testSetOptionsAcceptsOptionalSourcemap(): void + { + $minifier = new Horde_JavascriptMinify_Uglifyjs('var x = 1;', [ + 'uglifyjs' => '/usr/bin/uglifyjs', + 'sourcemap' => 'https://example.com/sourcemap', + ]); + + $this->assertInstanceOf(Horde_JavascriptMinify_Uglifyjs::class, $minifier); + } +} diff --git a/test/unit/YuiSetOptionsTest.php b/test/unit/YuiSetOptionsTest.php new file mode 100644 index 0000000..8768ec6 --- /dev/null +++ b/test/unit/YuiSetOptionsTest.php @@ -0,0 +1,62 @@ +expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('java'); + + new Horde_JavascriptMinify_Yui('var x = 1;', [ + 'yui' => '/usr/bin/yui.jar', + ]); + } + + public function testSetOptionsRequiresYuiOption(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('yui'); + + new Horde_JavascriptMinify_Yui('var x = 1;', [ + 'java' => '/usr/bin/java', + ]); + } + + public function testSetOptionsAcceptsValidOptions(): void + { + $minifier = new Horde_JavascriptMinify_Yui('var x = 1;', [ + 'java' => '/usr/bin/java', + 'yui' => '/usr/bin/yui.jar', + ]); + + $this->assertInstanceOf(Horde_JavascriptMinify_Yui::class, $minifier); + } + + public function testSetOptionsAcceptsOptionalCmdline(): void + { + $minifier = new Horde_JavascriptMinify_Yui('var x = 1;', [ + 'java' => '/usr/bin/java', + 'yui' => '/usr/bin/yui.jar', + 'cmdline' => '--charset utf-8', + ]); + + $this->assertInstanceOf(Horde_JavascriptMinify_Yui::class, $minifier); + } +}