From 8e89ad21a2dfa8f4f147225ae35cab83db4de2f9 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Wed, 22 Mar 2017 14:47:07 +0100 Subject: [PoC] JS Combiner Signed-off-by: Roeland Jago Douma --- core/Application.php | 10 +++ core/Controller/JsController.php | 80 +++++++++++++++++ core/routes.php | 1 + lib/private/Template/JSCombiner.php | 133 +++++++++++++++++++++++++++++ lib/private/Template/JSResourceLocator.php | 27 +++++- lib/private/TemplateLayout.php | 8 +- 6 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 core/Controller/JsController.php create mode 100644 lib/private/Template/JSCombiner.php diff --git a/core/Application.php b/core/Application.php index 6621964c289..33b7dd1b333 100644 --- a/core/Application.php +++ b/core/Application.php @@ -30,6 +30,8 @@ namespace OC\Core; +use OC\AppFramework\Utility\SimpleContainer; +use OC\Core\Controller\JsController; use OC\Core\Controller\OCJSController; use OC\Security\IdentityProof\Manager; use OC\Server; @@ -87,5 +89,13 @@ class Application extends App { $server->getURLGenerator() ); }); + $container->registerService(JsController::class, function () use ($container) { + return new JsController( + $container->query('AppName'), + $container->query(IRequest::class), + $container->getServer()->getAppDataDir('js'), + $container->query(ITimeFactory::class) + ); + }); } } diff --git a/core/Controller/JsController.php b/core/Controller/JsController.php new file mode 100644 index 00000000000..0770974e7a1 --- /dev/null +++ b/core/Controller/JsController.php @@ -0,0 +1,80 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OC\Core\Controller; + +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\NotFoundResponse; +use OCP\AppFramework\Http\FileDisplayResponse; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\IRequest; + +class JsController extends Controller { + + /** @var IAppData */ + protected $appData; + + /** @var ITimeFactory */ + protected $timeFactory; + + /** + * @param string $appName + * @param IRequest $request + * @param IAppData $appData + * @param ITimeFactory $timeFactory + */ + public function __construct($appName, IRequest $request, IAppData $appData, ITimeFactory $timeFactory) { + parent::__construct($appName, $request); + + $this->appData = $appData; + $this->timeFactory = $timeFactory; + } + + /** + * @PublicPage + * @NoCSRFRequired + * + * @param string $fileName css filename with extension + * @param string $appName css folder name + * @return FileDisplayResponse|NotFoundResponse + */ + public function getJs($fileName, $appName) { + try { + $folder = $this->appData->getFolder($appName); + $jsFile = $folder->getFile($fileName); + } catch(NotFoundException $e) { + return new NotFoundResponse(); + } + + $response = new FileDisplayResponse($jsFile, Http::STATUS_OK, ['Content-Type' => 'application/javascript']); + $response->cacheFor(86400); + $expires = new \DateTime(); + $expires->setTimestamp($this->timeFactory->getTime()); + $expires->add(new \DateInterval('PT24H')); + $response->addHeader('Expires', $expires->format(\DateTime::RFC1123)); + $response->addHeader('Pragma', 'cache'); + return $response; + } +} diff --git a/core/routes.php b/core/routes.php index 5d61d58e037..d3356404fd5 100644 --- a/core/routes.php +++ b/core/routes.php @@ -56,6 +56,7 @@ $application->registerRoutes($this, [ ['name' => 'Preview#getPreview', 'url' => '/core/preview', 'verb' => 'GET'], ['name' => 'Preview#getPreview', 'url' => '/core/preview.png', 'verb' => 'GET'], ['name' => 'Css#getCss', 'url' => '/css/{appName}/{fileName}', 'verb' => 'GET'], + ['name' => 'Js#getJs', 'url' => '/js/{appName}/{fileName}', 'verb' => 'GET'], ], 'ocs' => [ ['root' => '/cloud', 'name' => 'OCS#getCapabilities', 'url' => '/capabilities', 'verb' => 'GET'], diff --git a/lib/private/Template/JSCombiner.php b/lib/private/Template/JSCombiner.php new file mode 100644 index 00000000000..10560c8edf4 --- /dev/null +++ b/lib/private/Template/JSCombiner.php @@ -0,0 +1,133 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OC\Template; + +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\Files\SimpleFS\ISimpleFolder; +use OCP\IURLGenerator; + +class JSCombiner { + + /** @var IAppData */ + protected $appData; + + /** @var IURLGenerator */ + protected $urlGenerator; + + /** + * JSCombiner constructor. + * + * @param IAppData $appData + * @param IURLGenerator $urlGenerator + */ + public function __construct(IAppData $appData, + IURLGenerator $urlGenerator) { + $this->appData = $appData; + $this->urlGenerator = $urlGenerator; + } + + /** + * @param string $root + * @param string $file + * @param string $app + * @return bool + */ + public function process($root, $file, $app) { + $path = explode('/', $root . '/' . $file); + + $fileName = array_pop($path); + $path = implode('/', $path); + + try { + $folder = $this->appData->getFolder($app); + } catch(NotFoundException $e) { + // creating css appdata folder + $folder = $this->appData->newFolder($app); + } + + if($this->isCached($fileName, $folder)) { + return true; + } + return $this->cache($path, $fileName, $folder); + } + + /** + * @param string $fileName + * @param ISimpleFolder $folder + * @return bool + */ + protected function isCached($fileName, ISimpleFolder $folder) { + return false; + } + + /** + * @param string $path + * @param string $fileName + * @param ISimpleFolder $folder + * @return bool + */ + protected function cache($path, $fileName, ISimpleFolder $folder) { + $data = json_decode(file_get_contents($path . '/' . $fileName)); + + $res = ''; + $deps = []; + foreach ($data as $file) { + $filePath = $path . '/' . $file; + + if (is_file($filePath)) { + $res .= file_get_contents($path . '/' . $file); + $res .= PHP_EOL . PHP_EOL; + $deps[$file] = filemtime($path . '/' . $file); + } + } + + $fileName = str_replace('.json', '.js', $fileName); + try { + $cachedfile = $folder->getFile($fileName); + } catch(NotFoundException $e) { + $cachedfile = $folder->newFile($fileName); + } + + try { + $cachedfile->putContent($res); + return true; + } catch (NotPermittedException $e) { + return false; + } + } + + /** + * @param string $appName + * @param string $fileName + * @return string + */ + public function getCachedJS($appName, $fileName) { + $tmpfileLoc = explode('/', $fileName); + $fileName = array_pop($tmpfileLoc); + $fileName = str_replace('.json', '.js', $fileName); + + return substr($this->urlGenerator->linkToRoute('core.Js.getJs', array('fileName' => $fileName, 'appName' => $appName)), strlen(\OC::$WEBROOT) + 1); + } +} diff --git a/lib/private/Template/JSResourceLocator.php b/lib/private/Template/JSResourceLocator.php index 724f49965b3..6f863e859d9 100644 --- a/lib/private/Template/JSResourceLocator.php +++ b/lib/private/Template/JSResourceLocator.php @@ -26,6 +26,16 @@ namespace OC\Template; class JSResourceLocator extends ResourceLocator { + + /** @var JSCombiner */ + protected $jsCombiner; + + public function __construct(\OCP\ILogger $logger, $theme, array $core_map, array $party_map, JSCombiner $JSCombiner) { + parent::__construct($logger, $theme, $core_map, $party_map); + + $this->jsCombiner = $JSCombiner; + } + /** * @param string $script */ @@ -52,8 +62,10 @@ class JSResourceLocator extends ResourceLocator { } else if ($this->appendIfExist($this->serverroot, $theme_dir.'apps/'.$script.'.js') || $this->appendIfExist($this->serverroot, $theme_dir.$script.'.js') || $this->appendIfExist($this->serverroot, $script.'.js') + || $this->cacheAndAppendCombineJsonIfExist($this->serverroot, $script.'.json') || $this->appendIfExist($this->serverroot, $theme_dir.'core/'.$script.'.js') || $this->appendIfExist($this->serverroot, 'core/'.$script.'.js') + || $this->cacheAndAppendCombineJsonIfExist($this->serverroot, 'core/'.$script.'.json') ) { return; } @@ -68,7 +80,9 @@ class JSResourceLocator extends ResourceLocator { $this->appendIfExist($app_path, $script . '.js', $app_url); return; } - $this->append($app_path, $script . '.js', $app_url); + if (!$this->cacheAndAppendCombineJsonIfExist($app_path, $script.'.json', $app)) { + $this->append($app_path, $script . '.js', $app_url); + } } /** @@ -76,4 +90,15 @@ class JSResourceLocator extends ResourceLocator { */ public function doFindTheme($script) { } + + protected function cacheAndAppendCombineJsonIfExist($root, $file, $app = 'core') { + if (is_file($root.'/'.$file)) { + if ($this->jsCombiner->process($root, $file, $app)) { + $this->append($this->serverroot, $this->jsCombiner->getCachedJS($app, $file), false, false); + return true; + } + } + + return false; + } } diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index 3206a1d3ba8..064e590b20a 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -35,6 +35,7 @@ */ namespace OC; +use OC\Template\JSCombiner; use OC\Template\JSConfigHelper; use OC\Template\SCSSCacher; @@ -249,7 +250,12 @@ class TemplateLayout extends \OC_Template { \OC::$server->getLogger(), $theme, array( \OC::$SERVERROOT => \OC::$WEBROOT ), - array( \OC::$SERVERROOT => \OC::$WEBROOT )); + array( \OC::$SERVERROOT => \OC::$WEBROOT ), + new JSCombiner( + \OC::$server->getAppDataDir('js'), + \OC::$server->getURLGenerator() + ) + ); $locator->find($scripts); return $locator->getResources(); } -- cgit v1.2.3 From 48158c8becff9bab6dd6b3d3cd7047aca1290dfd Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Wed, 22 Mar 2017 14:47:32 +0100 Subject: Combine additional files_sharing scripts Signed-off-by: Roeland Jago Douma --- apps/files_sharing/appinfo/app.php | 4 +--- apps/files_sharing/js/additionalScripts.json | 5 +++++ 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 apps/files_sharing/js/additionalScripts.json diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php index fdaf3d1ec6c..9a06f40abf5 100644 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -38,10 +38,8 @@ $eventDispatcher = \OC::$server->getEventDispatcher(); $eventDispatcher->addListener( 'OCA\Files::loadAdditionalScripts', function() { - \OCP\Util::addScript('files_sharing', 'share'); - \OCP\Util::addScript('files_sharing', 'sharetabview'); - \OCP\Util::addScript('files_sharing', 'sharebreadcrumbview'); \OCP\Util::addStyle('files_sharing', 'mergedAdditionalStyles'); + \OCP\Util::addScript('files_sharing', 'additionalScripts'); } ); diff --git a/apps/files_sharing/js/additionalScripts.json b/apps/files_sharing/js/additionalScripts.json new file mode 100644 index 00000000000..81f3e9f3cab --- /dev/null +++ b/apps/files_sharing/js/additionalScripts.json @@ -0,0 +1,5 @@ +[ + "share.js", + "sharetabview.js", + "sharebreadcrumbview.js" +] -- cgit v1.2.3 From 242f8964cf8e98726a4d6e51cfd5200b17211e3e Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Wed, 22 Mar 2017 15:20:29 +0100 Subject: Add caching Signed-off-by: Roeland Jago Douma --- lib/private/Template/JSCombiner.php | 48 ++++++++++++++++++++++++++++++++----- lib/private/TemplateLayout.php | 3 ++- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/lib/private/Template/JSCombiner.php b/lib/private/Template/JSCombiner.php index 10560c8edf4..f470ee330c7 100644 --- a/lib/private/Template/JSCombiner.php +++ b/lib/private/Template/JSCombiner.php @@ -22,6 +22,7 @@ */ namespace OC\Template; +use OCP\ICache; use OCP\Files\IAppData; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; @@ -36,16 +37,22 @@ class JSCombiner { /** @var IURLGenerator */ protected $urlGenerator; + /** @var ICache */ + protected $depsCache; + /** * JSCombiner constructor. * * @param IAppData $appData * @param IURLGenerator $urlGenerator + * @param ICache $depsCache */ public function __construct(IAppData $appData, - IURLGenerator $urlGenerator) { + IURLGenerator $urlGenerator, + ICache $depsCache) { $this->appData = $appData; $this->urlGenerator = $urlGenerator; + $this->depsCache = $depsCache; } /** @@ -79,7 +86,26 @@ class JSCombiner { * @return bool */ protected function isCached($fileName, ISimpleFolder $folder) { - return false; + $fileName = str_replace('.json', '.js', $fileName) . '.deps'; + try { + $deps = $this->depsCache->get($folder->getName() . '-' . $fileName); + if ($deps === null) { + $depFile = $folder->getFile($fileName); + $deps = $depFile->getContent(); + $this->depsCache->set($folder->getName() . '-' . $fileName, $deps); + } + $deps = json_decode($deps, true); + + foreach ($deps as $file=>$mtime) { + if (!file_exists($file) || filemtime($file) > $mtime) { + return false; + } + } + + return true; + } catch(NotFoundException $e) { + return false; + } } /** @@ -89,17 +115,19 @@ class JSCombiner { * @return bool */ protected function cache($path, $fileName, ISimpleFolder $folder) { - $data = json_decode(file_get_contents($path . '/' . $fileName)); + $deps = []; + $fullPath = $path . '/' . $fileName; + $data = json_decode(file_get_contents($fullPath)); + $deps[$fullPath] = filemtime($fullPath); $res = ''; - $deps = []; foreach ($data as $file) { $filePath = $path . '/' . $file; if (is_file($filePath)) { - $res .= file_get_contents($path . '/' . $file); + $res .= file_get_contents($filePath); $res .= PHP_EOL . PHP_EOL; - $deps[$file] = filemtime($path . '/' . $file); + $deps[$filePath] = filemtime($filePath); } } @@ -110,8 +138,16 @@ class JSCombiner { $cachedfile = $folder->newFile($fileName); } + $depFileName = $fileName . '.deps'; + try { + $depFile = $folder->getFile($depFileName); + } catch (NotFoundException $e) { + $depFile = $folder->newFile($depFileName); + } + try { $cachedfile->putContent($res); + $depFile->putContent(json_encode($deps)); return true; } catch (NotPermittedException $e) { return false; diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index 064e590b20a..d172da1184b 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -253,7 +253,8 @@ class TemplateLayout extends \OC_Template { array( \OC::$SERVERROOT => \OC::$WEBROOT ), new JSCombiner( \OC::$server->getAppDataDir('js'), - \OC::$server->getURLGenerator() + \OC::$server->getURLGenerator(), + \OC::$server->getMemCacheFactory()->create('JS') ) ); $locator->find($scripts); -- cgit v1.2.3 From 90910290d17390889b5f8ffe97de9105f23d094b Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Wed, 22 Mar 2017 15:42:17 +0100 Subject: Add debug mode Signed-off-by: Roeland Jago Douma --- lib/private/Template/JSCombiner.php | 32 +++++++++++++++++++++++++++++- lib/private/Template/JSResourceLocator.php | 8 ++++++++ lib/private/TemplateLayout.php | 3 ++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/lib/private/Template/JSCombiner.php b/lib/private/Template/JSCombiner.php index f470ee330c7..a7bbf129e01 100644 --- a/lib/private/Template/JSCombiner.php +++ b/lib/private/Template/JSCombiner.php @@ -22,6 +22,7 @@ */ namespace OC\Template; +use OC\SystemConfig; use OCP\ICache; use OCP\Files\IAppData; use OCP\Files\NotFoundException; @@ -40,6 +41,9 @@ class JSCombiner { /** @var ICache */ protected $depsCache; + /** @var SystemConfig */ + protected $config; + /** * JSCombiner constructor. * @@ -49,10 +53,12 @@ class JSCombiner { */ public function __construct(IAppData $appData, IURLGenerator $urlGenerator, - ICache $depsCache) { + ICache $depsCache, + SystemConfig $config) { $this->appData = $appData; $this->urlGenerator = $urlGenerator; $this->depsCache = $depsCache; + $this->config = $config; } /** @@ -62,6 +68,10 @@ class JSCombiner { * @return bool */ public function process($root, $file, $app) { + if ($this->config->getValue('debug')) { + return false; + } + $path = explode('/', $root . '/' . $file); $fileName = array_pop($path); @@ -166,4 +176,24 @@ class JSCombiner { return substr($this->urlGenerator->linkToRoute('core.Js.getJs', array('fileName' => $fileName, 'appName' => $appName)), strlen(\OC::$WEBROOT) + 1); } + + /** + * @param string $root + * @param string $file + * @return string[] + */ + public function getContent($root, $file) { + $data = json_decode(file_get_contents($root . '/' . $file)); + + $path = explode('/', $file); + array_pop($path); + $path = implode('/', $path); + + $result = []; + foreach ($data as $f) { + $result[] = $path . '/' . $f; + } + + return $result; + } } diff --git a/lib/private/Template/JSResourceLocator.php b/lib/private/Template/JSResourceLocator.php index 6f863e859d9..41b44c143ef 100644 --- a/lib/private/Template/JSResourceLocator.php +++ b/lib/private/Template/JSResourceLocator.php @@ -96,6 +96,14 @@ class JSResourceLocator extends ResourceLocator { if ($this->jsCombiner->process($root, $file, $app)) { $this->append($this->serverroot, $this->jsCombiner->getCachedJS($app, $file), false, false); return true; + } else { + // Add all the files from the json + $files = $this->jsCombiner->getContent($root, $file); + $app_url = \OC_App::getAppWebPath($app); + + foreach ($files as $jsFile) { + $this->append($root, $jsFile, $app_url); + } } } diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index d172da1184b..956cba40086 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -254,7 +254,8 @@ class TemplateLayout extends \OC_Template { new JSCombiner( \OC::$server->getAppDataDir('js'), \OC::$server->getURLGenerator(), - \OC::$server->getMemCacheFactory()->create('JS') + \OC::$server->getMemCacheFactory()->create('JS'), + \OC::$server->getSystemConfig() ) ); $locator->find($scripts); -- cgit v1.2.3 From b5299b14032ee004e191c5ed1f3c9c1f62db099e Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Wed, 22 Mar 2017 16:22:15 +0100 Subject: Add return Signed-off-by: Roeland Jago Douma --- lib/private/Template/JSResourceLocator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/Template/JSResourceLocator.php b/lib/private/Template/JSResourceLocator.php index 41b44c143ef..32a01565c69 100644 --- a/lib/private/Template/JSResourceLocator.php +++ b/lib/private/Template/JSResourceLocator.php @@ -95,7 +95,6 @@ class JSResourceLocator extends ResourceLocator { if (is_file($root.'/'.$file)) { if ($this->jsCombiner->process($root, $file, $app)) { $this->append($this->serverroot, $this->jsCombiner->getCachedJS($app, $file), false, false); - return true; } else { // Add all the files from the json $files = $this->jsCombiner->getContent($root, $file); @@ -105,6 +104,7 @@ class JSResourceLocator extends ResourceLocator { $this->append($root, $jsFile, $app_url); } } + return true; } return false; -- cgit v1.2.3 From 677e11b1a467d74f43bd9cf21280bc65a5cd1a52 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 24 Mar 2017 11:00:07 +0100 Subject: Tests Signed-off-by: Roeland Jago Douma --- tests/Core/Controller/JsControllerTest.php | 110 ++++++++++++ tests/lib/Template/JSCombinerTest.php | 268 +++++++++++++++++++++++++++++ tests/lib/Template/data/1.js | 1 + tests/lib/Template/data/2.js | 1 + tests/lib/Template/data/combine.json | 4 + 5 files changed, 384 insertions(+) create mode 100644 tests/Core/Controller/JsControllerTest.php create mode 100644 tests/lib/Template/JSCombinerTest.php create mode 100644 tests/lib/Template/data/1.js create mode 100644 tests/lib/Template/data/2.js create mode 100644 tests/lib/Template/data/combine.json diff --git a/tests/Core/Controller/JsControllerTest.php b/tests/Core/Controller/JsControllerTest.php new file mode 100644 index 00000000000..febb785f60d --- /dev/null +++ b/tests/Core/Controller/JsControllerTest.php @@ -0,0 +1,110 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace Tests\Core\Controller; + +use OC\Core\Controller\JsController; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\FileDisplayResponse; +use OCP\AppFramework\Http\NotFoundResponse; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\SimpleFS\ISimpleFolder; +use OCP\IRequest; +use Test\TestCase; + +class JsControllerTest extends TestCase { + + /** @var IAppData|\PHPUnit_Framework_MockObject_MockObject */ + private $appData; + + /** @var JsController */ + private $controller; + + public function setUp() { + parent::setUp(); + + $this->appData = $this->createMock(IAppData::class); + + $timeFactory = $this->createMock(ITimeFactory::class); + $timeFactory->method('getTime') + ->willReturn(1337); + + $this->controller = new JsController( + 'core', + $this->createMock(IRequest::class), + $this->appData, + $timeFactory + ); + } + + public function testNoCssFolderForApp() { + $this->appData->method('getFolder') + ->with('myapp') + ->willThrowException(new NotFoundException()); + + $result = $this->controller->getJs('file.css', 'myapp'); + + $this->assertInstanceOf(NotFoundResponse::class, $result); + } + + + public function testNoCssFile() { + $folder = $this->createMock(ISimpleFolder::class); + $this->appData->method('getFolder') + ->with('myapp') + ->willReturn($folder); + + $folder->method('getFile') + ->willThrowException(new NotFoundException()); + + $result = $this->controller->getJs('file.css', 'myapp'); + + $this->assertInstanceOf(NotFoundResponse::class, $result); + } + + public function testGetFile() { + $folder = $this->createMock(ISimpleFolder::class); + $file = $this->createMock(ISimpleFile::class); + $this->appData->method('getFolder') + ->with('myapp') + ->willReturn($folder); + + $folder->method('getFile') + ->with('file.js') + ->willReturn($file); + + $expected = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => 'application/javascript']); + $expected->cacheFor(86400); + $expires = new \DateTime(); + $expires->setTimestamp(1337); + $expires->add(new \DateInterval('PT24H')); + $expected->addHeader('Expires', $expires->format(\DateTime::RFC1123)); + $expected->addHeader('Pragma', 'cache'); + + $result = $this->controller->getJs('file.js', 'myapp'); + $this->assertEquals($expected, $result); + } + +} diff --git a/tests/lib/Template/JSCombinerTest.php b/tests/lib/Template/JSCombinerTest.php new file mode 100644 index 00000000000..6e4ef5735dc --- /dev/null +++ b/tests/lib/Template/JSCombinerTest.php @@ -0,0 +1,268 @@ +appData = $this->createMock(IAppData::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->config = $this->createMock(SystemConfig::class); + $this->depsCache = $this->createMock(ICache::class); + $this->jsCombiner = new JSCombiner( + $this->appData, + $this->urlGenerator, + $this->depsCache, + $this->config); + } + + public function testProcessUncachedFileNoAppDataFolder() { + $folder = $this->createMock(ISimpleFolder::class); + $this->appData->expects($this->once())->method('getFolder')->with('awesomeapp')->willThrowException(new NotFoundException()); + $this->appData->expects($this->once())->method('newFolder')->with('awesomeapp')->willReturn($folder); + $file = $this->createMock(ISimpleFile::class); + + $fileDeps = $this->createMock(ISimpleFile::class); + + $folder->method('getFile') + ->will($this->returnCallback(function($path) use ($file) { + if ($path === 'combine.js') { + return $file; + } else if ($path === 'combine.js.deps') { + throw new NotFoundException(); + } else { + $this->fail(); + } + })); + $folder->expects($this->once()) + ->method('newFile') + ->with('combine.js.deps') + ->willReturn($fileDeps); + + $actual = $this->jsCombiner->process(__DIR__, '/data/combine.json', 'awesomeapp'); + $this->assertTrue($actual); + } + + public function testProcessUncachedFile() { + $folder = $this->createMock(ISimpleFolder::class); + $this->appData->expects($this->once())->method('getFolder')->with('awesomeapp')->willReturn($folder); + $file = $this->createMock(ISimpleFile::class); + + $fileDeps = $this->createMock(ISimpleFile::class); + + $folder->method('getFile') + ->will($this->returnCallback(function($path) use ($file) { + if ($path === 'combine.js') { + return $file; + } else if ($path === 'combine.js.deps') { + throw new NotFoundException(); + } else { + $this->fail(); + } + })); + $folder->expects($this->once()) + ->method('newFile') + ->with('combine.js.deps') + ->willReturn($fileDeps); + + $actual = $this->jsCombiner->process(__DIR__, '/data/combine.json', 'awesomeapp'); + $this->assertTrue($actual); + } + + public function testProcessCachedFile() { + $folder = $this->createMock(ISimpleFolder::class); + $this->appData->expects($this->once())->method('getFolder')->with('awesomeapp')->willReturn($folder); + $file = $this->createMock(ISimpleFile::class); + + $fileDeps = $this->createMock(ISimpleFile::class); + + $fileDeps->expects($this->once())->method('getContent')->willReturn('{}'); + + $folder->method('getFile') + ->will($this->returnCallback(function($path) use ($file, $fileDeps) { + if ($path === 'combine.js') { + return $file; + } else if ($path === 'combine.js.deps') { + return $fileDeps; + } else { + $this->fail(); + } + })); + + $actual = $this->jsCombiner->process(__DIR__, '/data/combine.json', 'awesomeapp'); + $this->assertTrue($actual); + } + + public function testProcessCachedFileMemcache() { + $folder = $this->createMock(ISimpleFolder::class); + $this->appData->expects($this->once()) + ->method('getFolder') + ->with('awesomeapp') + ->willReturn($folder); + $folder->method('getName') + ->willReturn('awesomeapp'); + + $file = $this->createMock(ISimpleFile::class); + + $this->depsCache->method('get') + ->with('awesomeapp-combine.js.deps') + ->willReturn('{}'); + + $folder->method('getFile') + ->will($this->returnCallback(function($path) use ($file) { + if ($path === 'combine.js') { + return $file; + } else if ($path === 'combine.js.deps') { + $this->fail(); + } else { + $this->fail(); + } + })); + + $actual = $this->jsCombiner->process(__DIR__, '/data/combine.json', 'awesomeapp'); + $this->assertTrue($actual); + } + + public function testIsCachedNoDepsFile() { + $fileName = "combine.json"; + $folder = $this->createMock(ISimpleFolder::class); + $file = $this->createMock(ISimpleFile::class); + + $folder->method('getFile') + ->will($this->returnCallback(function($path) use ($file) { + if ($path === 'combine.js') { + return $file; + } else if ($path === 'combine.js.deps') { + throw new NotFoundException(); + } else { + $this->fail(); + } + })); + + $actual = self::invokePrivate($this->jsCombiner, 'isCached', [$fileName, $folder]); + $this->assertFalse($actual); + } + public function testCacheNoFile() { + $fileName = "combine.js"; + + $folder = $this->createMock(ISimpleFolder::class); + $file = $this->createMock(ISimpleFile::class); + $depsFile = $this->createMock(ISimpleFile::class); + + $path = __DIR__ . '/data/'; + + $folder->expects($this->at(0))->method('getFile')->with($fileName)->willThrowException(new NotFoundException()); + $folder->expects($this->at(1))->method('newFile')->with($fileName)->willReturn($file); + $folder->expects($this->at(2))->method('getFile')->with($fileName . '.deps')->willThrowException(new NotFoundException()); + $folder->expects($this->at(3))->method('newFile')->with($fileName . '.deps')->willReturn($depsFile); + + $file->expects($this->once())->method('putContent'); + $depsFile->expects($this->once())->method('putContent'); + + $actual = self::invokePrivate($this->jsCombiner, 'cache', [$path, 'combine.json', $folder]); + $this->assertTrue($actual); + } + + public function testCache() { + $fileName = "combine.js"; + + $folder = $this->createMock(ISimpleFolder::class); + $file = $this->createMock(ISimpleFile::class); + $depsFile = $this->createMock(ISimpleFile::class); + + $path = __DIR__ . '/data/'; + + $folder->expects($this->at(0))->method('getFile')->with($fileName)->willReturn($file); + $folder->expects($this->at(1))->method('getFile')->with($fileName . '.deps')->willReturn($depsFile); + + $file->expects($this->once())->method('putContent'); + $depsFile->expects($this->once())->method('putContent'); + + $actual = self::invokePrivate($this->jsCombiner, 'cache', [$path, 'combine.json', $folder]); + $this->assertTrue($actual); + } + + public function testCacheSuccess() { + $fileName = 'combine.js'; + + $folder = $this->createMock(ISimpleFolder::class); + $file = $this->createMock(ISimpleFile::class); + $depsFile = $this->createMock(ISimpleFile::class); + + $path = __DIR__ . '/data/'; + + $folder->expects($this->at(0))->method('getFile')->with($fileName)->willReturn($file); + $folder->expects($this->at(1))->method('getFile')->with($fileName . '.deps')->willReturn($depsFile); + + $file->expects($this->at(0)) + ->method('putContent') + ->with('var a = \'hello\'; + + +var b = \'world\'; + + +'); + $depsFile->expects($this->at(0))->method('putContent')->with($this->callback( + function ($content) { + $deps = json_decode($content, true); + return array_key_exists(__DIR__ . '/data//1.js', $deps) + && array_key_exists(__DIR__ . '/data//2.js', $deps); + })); + + $actual = self::invokePrivate($this->jsCombiner, 'cache', [$path, 'combine.json', $folder]); + $this->assertTrue($actual); + } + + public function dataGetCachedSCSS() { + return [ + ['awesomeapp', 'core/js/foo.json', '/js/core/foo.js'], + ['files', 'apps/files/js/foo.json', '/js/files/foo.js'] + ]; + } + + /** + * @param $appName + * @param $fileName + * @param $result + * @dataProvider dataGetCachedSCSS + */ + public function testGetCachedSCSS($appName, $fileName, $result) { + $this->urlGenerator->expects($this->once()) + ->method('linkToRoute') + ->with('core.Js.getJs', [ + 'fileName' => 'foo.js', + 'appName' => $appName + ]) + ->willReturn(\OC::$WEBROOT . $result); + + $actual = $this->jsCombiner->getCachedJS($appName, $fileName); + $this->assertEquals(substr($result, 1), $actual); + } + + +} diff --git a/tests/lib/Template/data/1.js b/tests/lib/Template/data/1.js new file mode 100644 index 00000000000..ab3d260180e --- /dev/null +++ b/tests/lib/Template/data/1.js @@ -0,0 +1 @@ +var a = 'hello'; diff --git a/tests/lib/Template/data/2.js b/tests/lib/Template/data/2.js new file mode 100644 index 00000000000..4fd3078d7ab --- /dev/null +++ b/tests/lib/Template/data/2.js @@ -0,0 +1 @@ +var b = 'world'; diff --git a/tests/lib/Template/data/combine.json b/tests/lib/Template/data/combine.json new file mode 100644 index 00000000000..e727cbdba8d --- /dev/null +++ b/tests/lib/Template/data/combine.json @@ -0,0 +1,4 @@ +[ + "1.js", + "2.js" +] -- cgit v1.2.3 From be6acbeb52129d479488e8a059ce5e43c44747cf Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 24 Mar 2017 16:11:54 +0100 Subject: Update autoloader Signed-off-by: Roeland Jago Douma --- lib/composer/composer/autoload_classmap.php | 2 ++ lib/composer/composer/autoload_static.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 5e51eab7334..f009c0be203 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -439,6 +439,7 @@ return array( 'OC\\Core\\Command\\User\\Setting' => $baseDir . '/core/Command/User/Setting.php', 'OC\\Core\\Controller\\AvatarController' => $baseDir . '/core/Controller/AvatarController.php', 'OC\\Core\\Controller\\CssController' => $baseDir . '/core/Controller/CssController.php', + 'OC\\Core\\Controller\\JsController' => $baseDir . '/core/Controller/JsController.php', 'OC\\Core\\Controller\\LoginController' => $baseDir . '/core/Controller/LoginController.php', 'OC\\Core\\Controller\\LostController' => $baseDir . '/core/Controller/LostController.php', 'OC\\Core\\Controller\\OCJSController' => $baseDir . '/core/Controller/OCJSController.php', @@ -802,6 +803,7 @@ return array( 'OC\\TemplateLayout' => $baseDir . '/lib/private/TemplateLayout.php', 'OC\\Template\\Base' => $baseDir . '/lib/private/Template/Base.php', 'OC\\Template\\CSSResourceLocator' => $baseDir . '/lib/private/Template/CSSResourceLocator.php', + 'OC\\Template\\JSCombiner' => $baseDir . '/lib/private/Template/JSCombiner.php', 'OC\\Template\\JSConfigHelper' => $baseDir . '/lib/private/Template/JSConfigHelper.php', 'OC\\Template\\JSResourceLocator' => $baseDir . '/lib/private/Template/JSResourceLocator.php', 'OC\\Template\\ResourceLocator' => $baseDir . '/lib/private/Template/ResourceLocator.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index b306831f239..9a6a41d8a37 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -469,6 +469,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Core\\Command\\User\\Setting' => __DIR__ . '/../../..' . '/core/Command/User/Setting.php', 'OC\\Core\\Controller\\AvatarController' => __DIR__ . '/../../..' . '/core/Controller/AvatarController.php', 'OC\\Core\\Controller\\CssController' => __DIR__ . '/../../..' . '/core/Controller/CssController.php', + 'OC\\Core\\Controller\\JsController' => __DIR__ . '/../../..' . '/core/Controller/JsController.php', 'OC\\Core\\Controller\\LoginController' => __DIR__ . '/../../..' . '/core/Controller/LoginController.php', 'OC\\Core\\Controller\\LostController' => __DIR__ . '/../../..' . '/core/Controller/LostController.php', 'OC\\Core\\Controller\\OCJSController' => __DIR__ . '/../../..' . '/core/Controller/OCJSController.php', @@ -832,6 +833,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\TemplateLayout' => __DIR__ . '/../../..' . '/lib/private/TemplateLayout.php', 'OC\\Template\\Base' => __DIR__ . '/../../..' . '/lib/private/Template/Base.php', 'OC\\Template\\CSSResourceLocator' => __DIR__ . '/../../..' . '/lib/private/Template/CSSResourceLocator.php', + 'OC\\Template\\JSCombiner' => __DIR__ . '/../../..' . '/lib/private/Template/JSCombiner.php', 'OC\\Template\\JSConfigHelper' => __DIR__ . '/../../..' . '/lib/private/Template/JSConfigHelper.php', 'OC\\Template\\JSResourceLocator' => __DIR__ . '/../../..' . '/lib/private/Template/JSResourceLocator.php', 'OC\\Template\\ResourceLocator' => __DIR__ . '/../../..' . '/lib/private/Template/ResourceLocator.php', -- cgit v1.2.3