From d067645a2601be8256ae758d1612e8fda197cc56 Mon Sep 17 00:00:00 2001 From: Terre Porter Date: Mon, 15 Apr 2019 11:07:06 -0400 Subject: [PATCH] Major update to package - Now uses Laravel auto registration feature. - No long need to remove 'Illuminate\View\ViewServiceProvider::class' from 'config/app.php' - The ability to 'setRawTags', 'setContentTags', and 'setEscapedTags' have been removed from Laravel (https://github.com/laravel/framework/issues/17736). They are depreciated here. - The compiler function 'setDeleteViewCacheAfterRender', has been deprecated as I didn't find any code where it was actually being used. - Added more tests --- README.md | 186 ++--- composer.json | 12 +- {src/config => config}/blade.php | 0 phpunit.xml | 50 +- src/Compilers/BladeCompiler.php | 95 +-- src/Compilers/StringBladeCompiler.php | 182 +++-- src/Engines/CompilerEngine.php | 4 +- src/Facade/StringBlade.php | 32 + src/Facades/StringBlade.php | 21 - src/Factory.php | 497 +------------- src/FileViewFinder.php | 299 -------- ...der.php => StringBladeServiceProvider.php} | 177 +++-- src/StringView.php | 126 ++-- src/View.php | 19 +- src/ViewFinderInterface.php | 64 -- tests/View/Blade/AbstractBladeTestCase.php | 27 + tests/View/Blade/BladeAppendTest.php | 11 + tests/View/Blade/BladeBreakStatementsTest.php | 71 ++ tests/View/Blade/BladeCanStatementsTest.php | 21 + .../View/Blade/BladeCananyStatementsTest.php | 21 + .../View/Blade/BladeCannotStatementsTest.php | 21 + tests/View/Blade/BladeCommentsTest.php | 27 + .../Blade/BladeContinueStatementsTest.php | 71 ++ tests/View/Blade/BladeCustomTest.php | 216 ++++++ tests/View/Blade/BladeEachTest.php | 12 + tests/View/Blade/BladeEchoTest.php | 66 ++ .../Blade/BladeElseAuthStatementsTest.php | 53 ++ .../Blade/BladeElseGuestStatementsTest.php | 37 + .../View/Blade/BladeElseIfStatementsTest.php | 21 + tests/View/Blade/BladeElseStatementsTest.php | 36 + tests/View/Blade/BladeEndSectionsTest.php | 11 + tests/View/Blade/BladeEscapedTest.php | 19 + tests/View/Blade/BladeExpressionTest.php | 18 + tests/View/Blade/BladeExtendsTest.php | 31 + tests/View/Blade/BladeForStatementsTest.php | 32 + .../View/Blade/BladeForeachStatementsTest.php | 85 +++ .../View/Blade/BladeForelseStatementsTest.php | 80 +++ tests/View/Blade/BladeHasSectionTest.php | 17 + tests/View/Blade/BladeHelpersTest.php | 15 + .../View/Blade/BladeIfAuthStatementsTest.php | 45 ++ .../View/Blade/BladeIfEmptyStatementsTest.php | 17 + .../View/Blade/BladeIfGuestStatementsTest.php | 33 + .../View/Blade/BladeIfIssetStatementsTest.php | 17 + tests/View/Blade/BladeIfStatementsTest.php | 56 ++ tests/View/Blade/BladeIncludeFirstTest.php | 12 + tests/View/Blade/BladeIncludeIfTest.php | 12 + tests/View/Blade/BladeIncludeTest.php | 12 + tests/View/Blade/BladeIncludeWhenTest.php | 12 + tests/View/Blade/BladeJsonTest.php | 22 + tests/View/Blade/BladeLangTest.php | 19 + .../View/Blade/BladeOverwriteSectionTest.php | 11 + tests/View/Blade/BladePhpStatementsTest.php | 46 ++ tests/View/Blade/BladePrependTest.php | 18 + tests/View/Blade/BladePushTest.php | 17 + tests/View/Blade/BladeSectionTest.php | 12 + tests/View/Blade/BladeShowTest.php | 11 + tests/View/Blade/BladeStackTest.php | 13 + tests/View/Blade/BladeStopSectionTest.php | 11 + .../View/Blade/BladeUnlessStatementsTest.php | 17 + tests/View/Blade/BladeUnsetStatementsTest.php | 13 + tests/View/Blade/BladeVerbatimTest.php | 89 +++ tests/View/Blade/BladeWhileStatementsTest.php | 32 + tests/View/Blade/BladeYieldTest.php | 13 + tests/View/DataObjectStub.php | 7 + .../StringViewTest.php | 158 +++-- tests/View/ViewCompilerEngineTest.php | 46 ++ .../ViewFactoryTest.php | 475 ++++++++++--- tests/View/ViewStringBladeCompilerTest.php | 196 ++++++ .../ViewTest.php | 132 ++-- tests/View/fixtures/basic.php | 2 + tests/View/fixtures/component.php | 1 + tests/View/fixtures/namespaced/basic.php | 1 + tests/View/fixtures/nested/basic.php | 1 + tests/View/fixtures/nested/child.php | 1 + .../fixtures/section-exception-layout.php | 0 .../fixtures/section-exception.php | 2 +- tests/bootstrap.php | 32 + .../ViewBladeCompilerTest.php | 637 ------------------ .../ViewFileViewFinderTest.php | 142 ---- 79 files changed, 2935 insertions(+), 2241 deletions(-) rename {src/config => config}/blade.php (100%) create mode 100644 src/Facade/StringBlade.php delete mode 100644 src/Facades/StringBlade.php delete mode 100644 src/FileViewFinder.php rename src/{ViewServiceProvider.php => StringBladeServiceProvider.php} (52%) delete mode 100644 src/ViewFinderInterface.php create mode 100644 tests/View/Blade/AbstractBladeTestCase.php create mode 100644 tests/View/Blade/BladeAppendTest.php create mode 100644 tests/View/Blade/BladeBreakStatementsTest.php create mode 100644 tests/View/Blade/BladeCanStatementsTest.php create mode 100644 tests/View/Blade/BladeCananyStatementsTest.php create mode 100644 tests/View/Blade/BladeCannotStatementsTest.php create mode 100644 tests/View/Blade/BladeCommentsTest.php create mode 100644 tests/View/Blade/BladeContinueStatementsTest.php create mode 100644 tests/View/Blade/BladeCustomTest.php create mode 100644 tests/View/Blade/BladeEachTest.php create mode 100644 tests/View/Blade/BladeEchoTest.php create mode 100644 tests/View/Blade/BladeElseAuthStatementsTest.php create mode 100644 tests/View/Blade/BladeElseGuestStatementsTest.php create mode 100644 tests/View/Blade/BladeElseIfStatementsTest.php create mode 100644 tests/View/Blade/BladeElseStatementsTest.php create mode 100644 tests/View/Blade/BladeEndSectionsTest.php create mode 100644 tests/View/Blade/BladeEscapedTest.php create mode 100644 tests/View/Blade/BladeExpressionTest.php create mode 100644 tests/View/Blade/BladeExtendsTest.php create mode 100644 tests/View/Blade/BladeForStatementsTest.php create mode 100644 tests/View/Blade/BladeForeachStatementsTest.php create mode 100644 tests/View/Blade/BladeForelseStatementsTest.php create mode 100644 tests/View/Blade/BladeHasSectionTest.php create mode 100644 tests/View/Blade/BladeHelpersTest.php create mode 100644 tests/View/Blade/BladeIfAuthStatementsTest.php create mode 100644 tests/View/Blade/BladeIfEmptyStatementsTest.php create mode 100644 tests/View/Blade/BladeIfGuestStatementsTest.php create mode 100644 tests/View/Blade/BladeIfIssetStatementsTest.php create mode 100644 tests/View/Blade/BladeIfStatementsTest.php create mode 100644 tests/View/Blade/BladeIncludeFirstTest.php create mode 100644 tests/View/Blade/BladeIncludeIfTest.php create mode 100644 tests/View/Blade/BladeIncludeTest.php create mode 100644 tests/View/Blade/BladeIncludeWhenTest.php create mode 100644 tests/View/Blade/BladeJsonTest.php create mode 100644 tests/View/Blade/BladeLangTest.php create mode 100644 tests/View/Blade/BladeOverwriteSectionTest.php create mode 100644 tests/View/Blade/BladePhpStatementsTest.php create mode 100644 tests/View/Blade/BladePrependTest.php create mode 100644 tests/View/Blade/BladePushTest.php create mode 100644 tests/View/Blade/BladeSectionTest.php create mode 100644 tests/View/Blade/BladeShowTest.php create mode 100644 tests/View/Blade/BladeStackTest.php create mode 100644 tests/View/Blade/BladeStopSectionTest.php create mode 100644 tests/View/Blade/BladeUnlessStatementsTest.php create mode 100644 tests/View/Blade/BladeUnsetStatementsTest.php create mode 100644 tests/View/Blade/BladeVerbatimTest.php create mode 100644 tests/View/Blade/BladeWhileStatementsTest.php create mode 100644 tests/View/Blade/BladeYieldTest.php create mode 100644 tests/View/DataObjectStub.php rename tests/{string_blade_compiler => View}/StringViewTest.php (55%) create mode 100644 tests/View/ViewCompilerEngineTest.php rename tests/{string_blade_compiler => View}/ViewFactoryTest.php (50%) create mode 100644 tests/View/ViewStringBladeCompilerTest.php rename tests/{string_blade_compiler => View}/ViewTest.php (62%) create mode 100644 tests/View/fixtures/basic.php create mode 100644 tests/View/fixtures/component.php create mode 100644 tests/View/fixtures/namespaced/basic.php create mode 100644 tests/View/fixtures/nested/basic.php create mode 100644 tests/View/fixtures/nested/child.php rename tests/{string_blade_compiler => View}/fixtures/section-exception-layout.php (100%) rename tests/{string_blade_compiler => View}/fixtures/section-exception.php (51%) create mode 100644 tests/bootstrap.php delete mode 100644 tests/string_blade_compiler/ViewBladeCompilerTest.php delete mode 100644 tests/string_blade_compiler/ViewFileViewFinderTest.php diff --git a/README.md b/README.md index 3da55b1..95a8374 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,40 @@ String Blade Compiler ======================= -[![Laravel 5.2](https://img.shields.io/badge/Laravel-5.2-orange.svg?style=flat-square)](http://laravel.com) +[![Laravel 5.8](https://img.shields.io/badge/Laravel-5.8-orange.svg?style=flat-square)](http://laravel.com) [![License](http://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://tldrlegal.com/license/mit-license) Render Blade templates from string value. Reworked version to allow for array to be passed to the view function instead of template file name. -This is a direct extension of \Illuminate\View\View and replaces the default. +> This is a direct extension of \Illuminate\View\View and is build to replace its usage. It will replace the default View instance. -Version +Versions ======================= -This version 1 is for Laravel 4.2, version 2 is for Laravel 5. - -Version 3 is a complete rewrite, for Laravel 5.1 - -Version 3.2 is a version for Laravel 5.2. - -Version 3.3 is a version for Laravel 5.3. - -Version 3.4 is a version for Laravel 5.4. - -Version 3.5 is a version for Laravel 5.5. - -Version 3.6 is a version for Laravel 5.6. +| String Blade | Laravel Version | +| ------------- |----------------:| +| 3.8 | Laravel 5.8 | +| 3.7 | Laravel 5.7 | +| 3.6 | Laravel 5.6 | +| 3.5 | Laravel 5.5 | +| 3.4 | Laravel 5.4 | +| 3.3 | Laravel 5.2 | +| 3.2 | Laravel 5.1 | +| 2.* | Laravel 5 | +| 1.* | Laravel 4.2 | + +Version 3.8 : Updates +======================= +> The package has been completely rewritten, all code updated to be more in line with the Laravel version code. Several of the functions in the extended class were not needed and have been removed and the code has been cleaned up a bit. -Version 3.7 is a version for Laravel 5.7. +> Also updated the tests to what is available in Laravel View Tests. Some are not applicable to StringBlade. -Version 3.8 is a version for Laravel 5.8. +Changes, +- Now uses Laravel auto registration feature. +- No long need to remove ```Illuminate\View\ViewServiceProvider::class``` from ```config/app.php``` +- The ability to ```setRawTags```, ```setContentTags```, and ```setEscapedTags``` have been removed from Laravel (https://github.com/laravel/framework/issues/17736). They are depreciated here. +- The compiler function ```setDeleteViewCacheAfterRender```, has been deprecated as I didn't find any code where it was actually being used. +- Added more tests Installation ======================= @@ -39,17 +46,27 @@ Add the package to composer.json: "wpb/string-blade-compiler": "VERSION" }, +> To get versions 'composer show wpb/string-blade-compiler', such as 'dev-master, * 3.2.x-dev, 3.2.0, 3.0.x-dev, 3.0.0, 2.1.0, 2.0.x-dev, 2.0.0, 1.0.x-dev, 1.0.0' + On packagist.org at https://packagist.org/packages/wpb/string-blade-compiler -Or from the console using require: composer require "wpb/string-blade-compiler" + composer require "wpb/string-blade-compiler" -To get versions 'composer show wpb/string-blade-compiler', such as 'dev-master, * 3.2.x-dev, 3.2.0, 3.0.x-dev, 3.0.0, 2.1.0, 2.0.x-dev, 2.0.0, 1.0.x-dev, 1.0.0' +Configuration +======================= In config\app.php, providers section: -Replace 'Illuminate\View\ViewServiceProvider::class' with 'Wpb\String_Blade_Compiler\ViewServiceProvider::class', - -There is no need to add a Facade to the aliases array as the service provider it is included automatically in the package's ServiceProvider. +> Both the ServiceProvider and Facade Alias are auto registered by Laravel. There is no need to add them to the /config/app.php file. + +~~Replace 'Illuminate\View\ViewServiceProvider::class' with 'Wpb\String_Blade_Compiler\ViewServiceProvider::class',~~ +> This version does not require you to remove the registration of the original view component. Upon ServiceProvider registration it will replace the view binds with its self. + +### Laravel's Autoloader + +> There currently is a issue in Laravel's preload process of ServiceProviders. Service providers that are registered with the autoloaded are instantiated before service providers that are set in /config/app.php. This may cause problems in prior versions of StringBladeCompiler. The version has been rewitten to account for the autoloading process. + +> **However, ** This could be an issue if any packages tries to use the StringBlade before it has been registered. A pull request has been filed to make Package Service Providers that are registered in /config/app.php be loaded before autoloaded ServiceProviders. The would allow manual registration of the package which would give it priority over autoload packages. Config ======================= @@ -58,95 +75,108 @@ Default cache time for the compiled string template is 300 seconds (5 mins), thi Note: If using homestead or some other vm, the host handles the filemtime of the cache file. This means the vm may have a different time than the file. If the cache is not expiring as expected, check the times between the systems. -Note: See new option below to delete view cache after rendering (works for both stringblade and blade compilers). - Usage ======================= This package offers a StringView facade with the same syntax as View but accepts a Array or Array Object instance instead of path to view. -The array value of 'updated_at' has been removed from this version, a new option called secondsTemplateCacheExpires has been added. +####New Config Option: -It is number of seconds since the template compiled file was last modified, as so 'time() >= ($this->files->lastModified($compiled) + $viewData->secondsTemplateCacheExpires)' +Laravel 5.8 BladeCompiler adds a php comment to the compiled template file ```php $contents .= "getPath()} ENDPATH**/ ?>"; ```. Since StringBladeCompiler does not have a "path" aka "template file location" that would be helpful to the developer. I have included a new config value, ```templateRefKey```. This allows the developer to tag the StringBladeCompiler for where it is used. This is for if you end up digging in to the compiled view files, it would allow you to see a tag for StingBladeCompiler files. -If cache_key is set, it will be used as the compiled key or a md5(template) is used. +####Config Options: ```php +// existing file template load (the original View() method +return view ('bladetemplatefile',['token' => 'I am the token value']); +``` +```php +// string blade template load +return view (['template' => '{{$token}}'], ['token' => 'I am the token value']); +``` -// existing file template load -return view ('bladetemplatefile', ['token' => 'I am the child template']); +```php +// you can mix the view types +$preset = view (['template' => '{{$token}}'], ['token' => 'I am the token value']); -// string template load -return view (['template' => '{{$token}}'], ['token' => 'I am the child template']); +return view ('bladetemplatefile', ['token' => $preset]); +``` +```php // full list of options return view( - array( - // this actual blade template - 'template' => '{{ $token1 }}', - // this is the cache file key, converted to md5 - 'cache_key' => 'my_unique_cache_key', - // number of seconds needed in order to recompile, 0 is always recompile - 'secondsTemplateCacheExpires' => 1391973007 - ), - array( - 'token1'=> 'token 1 value' - ) - ); + array( + // this actual blade template + 'template' => '{{ $token1 }}', + + // this is the cache file key, converted to md5 + 'cache_key' => 'my_unique_cache_key', + + // number of seconds needed in order to recompile, 0 is always recompile + 'secondsTemplateCacheExpires' => 1391973007, + + // sets the PATH comment value in the compiled file + 'templateRefKey' => 'IndexController: Build function' + ), + array( + 'token1'=> 'token 1 value' + ) + ); ``` -Also allows for Blade::extend, example : +> Since StringBlade is a extend class from the original View. You should be able to do anything you would normally do with a View using StringBlade. + +Blade::extend, for example : -Since the compilers are set up as seperate instances, if you need the extend on both the string and file template you will need to attach the extend (or directive) to both compilers. +As the compilers are set up as separate instances, if you need the extend on both the string and file template you will need to attach the extend (or directive) to both compilers. ```php - // allows for @continue and @break in foreach in blade templates - StringBlade::extend(function($value) - { - return preg_replace('/(\s*)@(break|continue)(\s*)/', '$1$3', $value); - }); - - Blade::extend(function($value) - { - return preg_replace('/(\s*)@(break|continue)(\s*)/', '$1$3', $value); - }); +// allows for @continue and @break in foreach in blade templates +StringBlade::extend(function($value) +{ + return preg_replace('/(\s*)@(break|continue)(\s*)/', '$1$3', $value); +}); + +Blade::extend(function($value) +{ + return preg_replace('/(\s*)@(break|continue)(\s*)/', '$1$3', $value); +}); ``` -New options, +Other options, ```php - // change the contente tags escaped or not - StringBlade::setContentTagsEscaped(true); - - // for devel force templates to be rebuilt, ignores secondsTemplateCacheExpires - StringBlade::setForceTemplateRecompile(true); -``` +// change the contente tags escaped or not +StringBlade::setContentTagsEscaped(true); -If you wish to use these with file templates, +// for devel force templates to be rebuilt, ignores secondsTemplateCacheExpires +StringBlade::setForceTemplateRecompile(true); +``` ```php - // change the contente tags escaped or not - Blade::setContentTagsEscaped(true); +// change the contente tags escaped or not +Blade::setContentTagsEscaped(true); - // for devel force templates to be rebuilt, ignores secondsTemplateCacheExpires - Blade::setForceTemplateRecompile(true); +// for devel force templates to be rebuilt, ignores secondsTemplateCacheExpires +Blade::setForceTemplateRecompile(true); ``` -Changing the tags - ```php - // change the tags - StringBlade::setRawTags('[!!', '!!]',escapeFlag); - StringBlade::setContentTags('[[', ']]',escapeFlag); - StringBlade::setEscapedContentTags('[[[', ']]]',escapeFlag); +// @deprecated This feature was removed from Laravel (https://github.com/laravel/framework/issues/17736) + +// change the tags +StringBlade::setRawTags('[!!', '!!]',escapeFlag); // default {!! !!} +StringBlade::setContentTags('[[', ']]',escapeFlag); // default {{ }} +StringBlade::setEscapedTags('[[[', ']]]',escapeFlag); // default {{{ }}} + +__ Functions are still there, use at your own risk. __ ``` -'escapeFlag', if true then the tags will be escaped, if false then they will not be escaped (same as setContentTagsEscaped function) +~~Deleting generated compiled cach view files (v3+).Set the delete flag for the compiler being used, stringblade or blade~~ -Deleting generated compiled cach view files (v3+), +> I can't seem to find when the setting was actually used. If you want this, submit a bug and I will see about adding the ability. -Set the delete flag for the compiler being used, stringblade or blade -``` +```php // set flag to delete compiled view cache files after rendering for stringblade compiler View::getEngineFromStringKey('stringblade')->setDeleteViewCacheAfterRender(true); diff --git a/composer.json b/composer.json index 29c7dee..8ee4dfc 100644 --- a/composer.json +++ b/composer.json @@ -18,5 +18,15 @@ "Wpb\\String_Blade_Compiler\\": "src/" } }, - "minimum-stability": "dev" + "minimum-stability": "dev", + "extra": { + "laravel": { + "providers": [ + "Wpb\\String_Blade_Compiler\\StringBladeServiceProvider" + ], + "aliases": { + "StringBlade": "Wpb\\String_Blade_Compiler\\Facades\\StringBlade" + } + } + } } diff --git a/src/config/blade.php b/config/blade.php similarity index 100% rename from src/config/blade.php rename to config/blade.php diff --git a/phpunit.xml b/phpunit.xml index 73e0002..75c2c2e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,19 +1,35 @@ - - - - - tests/string_blade_compiler/ - - - - - - src/ - - - - - - + + + + ./tests + + + + + ./src + + ./src/ + + + + + + + diff --git a/src/Compilers/BladeCompiler.php b/src/Compilers/BladeCompiler.php index 4f10120..de15d0a 100644 --- a/src/Compilers/BladeCompiler.php +++ b/src/Compilers/BladeCompiler.php @@ -2,7 +2,9 @@ namespace Wpb\String_Blade_Compiler\Compilers; -class BladeCompiler extends \Illuminate\View\Compilers\BladeCompiler +use Illuminate\View\Compilers\BladeCompiler as BladeCompilerParent; + +class BladeCompiler extends BladeCompilerParent { /** @@ -13,89 +15,76 @@ class BladeCompiler extends \Illuminate\View\Compilers\BladeCompiler /** * Switch to track escape setting for contentTags. * + * @deprecated Just use the escape tags {{{ }}} default + * * @var bool */ - protected $contentTagsEscaped = true; + // protected $contentTagsEscaped = true; /** - * Compile the "regular" echo statements. + * Array of opening and closing tags for raw echos. * - * @param string $value - * @return string + * @var array */ - protected function compileRegularEchos($value) - { - $pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->contentTags[0], $this->contentTags[1]); - - $callback = function ($matches) { - $whitespace = empty($matches[3]) ? '' : $matches[3].$matches[3]; - - if ($this->contentTagsEscaped) { - $wrapped = sprintf('e(%s)', $matches[2]); - } else { - $wrapped = sprintf('%s', $matches[2]); - } - - return $matches[1] ? substr($matches[0], 1) : ''.$whitespace; - }; - - return preg_replace_callback($pattern, $callback, $value); - } + protected $rawTags = ['{!!', '!!}']; /** - * Compile the escaped echo statements. + * Array of opening and closing tags for regular echos. * - * @param string $value - * @return string + * @var array */ - protected function compileEscapedEchos($value) - { - $pattern = sprintf('/%s\s*(.+?)\s*%s(\r?\n)?/s', $this->escapedTags[0], $this->escapedTags[1]); + protected $contentTags = ['{{', '}}']; - $callback = function ($matches) { - $whitespace = empty($matches[2]) ? '' : $matches[2].$matches[2]; - - return ''.$whitespace; - }; - - return preg_replace_callback($pattern, $callback, $value); - } + /** + * Array of opening and closing tags for escaped echos. + * + * @var array + */ + protected $escapedTags = ['{{{', '}}}']; /** * Sets the content tags used for the compiler. * + * @deprecated This feature was removed from Laravel (https://github.com/laravel/framework/issues/17736) + * * @param string $openTag * @param string $closeTag * @param bool $escaped * @return void */ - public function setContentTags($openTag, $closeTag, $escaped = true) + public function setRawTags($openTag, $closeTag, $escaped = true) { - $this->setContentTagsEscaped($escaped); - - $this->contentTags = [preg_quote($openTag), preg_quote($closeTag)]; + $this->rawTags = [preg_quote($openTag), preg_quote($closeTag)]; } /** - * Sets the escaped content tags used for the compiler. + * Sets the content tags used for the compiler. + * + * @deprecated This feature was removed from Laravel (https://github.com/laravel/framework/issues/17736) * * @param string $openTag * @param string $closeTag + * @param bool $escaped * @return void */ - public function setEscapedContentTags($openTag, $closeTag) + public function setContentTags($openTag, $closeTag, $escaped = true) { - $this->escapedTags = array(preg_quote($openTag), preg_quote($closeTag)); + $this->contentTags = [preg_quote($openTag), preg_quote($closeTag)]; } /** - * Enable/Disable escape setting for contentTags. + * Sets the escape tags used for the compiler. + * + * @deprecated This feature was removed from Laravel (https://github.com/laravel/framework/issues/17736) * - * @param bool $escaped + * @param string $openTag + * @param string $closeTag + * @param bool $escaped * @return void */ - public function setContentTagsEscaped($escaped = true) { - $this->contentTagsEscaped = $escaped; + public function setEscapeTags($openTag, $closeTag, $escaped = true) + { + $this->escapedTags = [preg_quote($openTag), preg_quote($closeTag)]; } /** @@ -125,14 +114,4 @@ public function isExpired($path) return parent::isExpired($path); } - /** - * Set the echo format to be used by the compiler. - * - * Removed in custom version - * - * @deprecated deprecated since version 1.0.0 - * @param string $format - * @return void * - */ - public function setEchoFormat($format) {} } diff --git a/src/Compilers/StringBladeCompiler.php b/src/Compilers/StringBladeCompiler.php index 33a4d17..0d6c277 100644 --- a/src/Compilers/StringBladeCompiler.php +++ b/src/Compilers/StringBladeCompiler.php @@ -3,76 +3,134 @@ namespace Wpb\String_Blade_Compiler\Compilers; use Config; +use Illuminate\Filesystem\Filesystem; +use Illuminate\Support\Str; use Illuminate\View\Compilers\CompilerInterface; +use InvalidArgumentException; -class StringBladeCompiler extends BladeCompiler implements CompilerInterface { - - /** - * Compile the view at the given path. - * - * @param object $viewData - * @return void - */ - public function compile($viewData = null) - { - - // get the template data - $string = $viewData->template; - - // Compile to PHP - $contents = $this->compileString($string); - - // check/save cache - if ( ! is_null($this->cachePath)) - { - $this->files->put($this->getCompiledPath($viewData), $contents); - - } - } - - /** - * Get the path to the compiled version of a view. - * - * @param object $viewData - * @return string - */ - public function getCompiledPath($viewData) - { - /* - * A unique path for the given model instance must be generated - * so the view has a place to cache. The following generates a - * path using almost the same logic as Blueprint::createIndexName() - */ - return $this->cachePath.'/'.$viewData->cache_key; - } - - /** - * Determine if the view at the given path is expired. - * - * @param string $viewData - * @return bool - */ - public function isExpired($viewData) - { - - $compiled = $this->getCompiledPath($viewData); - - // If the compiled file doesn't exist we will indicate that the view is expired - // so that it can be re-compiled. Else, we will verify the last modification - // of the views is less than the modification times of the compiled views. - if ( ! $this->cachePath || ! $this->files->exists($compiled)) - { - return true; - } +class StringBladeCompiler extends BladeCompiler { - // If set to 0, then return cache has expired - if ($viewData->secondsTemplateCacheExpires==0) { + private $use_cache_keys = []; + private $viewData = []; + + /** + * Create a new compiler instance. + * + * @param \Illuminate\Filesystem\Filesystem $files + * @param string $cachePath + * @return void + * + * @throws \InvalidArgumentException + */ + public function __construct(Filesystem $files, $cachePath) + { + $this->files = $files; + $this->cachePath = $cachePath; + } + + public function setViewData($viewData) { + $this->viewData = $viewData; + } + + /** + * Compile the view at the given path. + * + * @param object $viewData + * @return void + */ + public function compile($viewData = null) + { + if (!is_null($viewData)) { + $this->viewData = $viewData; + } + + if (property_exists($this->viewData, 'cache_key')) + { + $this->setPath($this->viewData->cache_key); + } + + $contents = $this->compileString( + $this->viewData->template + ); + + $tokens = $this->getOpenAndClosingPhpTokens($contents); + + // If the tokens we retrieved from the compiled contents have at least + // one opening tag and if that last token isn't the closing tag, we + // need to close the statement before adding the path at the end. + if ($tokens->isNotEmpty() && $tokens->last() !== T_CLOSE_TAG) { + $contents .= ' ?>'; + } + + if (isset($this->viewData->templateRefKey)) { + $contents .= "viewData->templateRefKey} ENDPATH**/ ?>"; + } + + if (! is_null($this->cachePath)) { + $this->files->put( + $this->getCompiledPath($this->viewData), $contents + ); + } + } + + /** + * Get the path to the compiled version of a view. + * + * @param string $path + * @return string + */ + public function getCompiledPath($viewData) + { + if (!property_exists($viewData, 'cache_key')) + { + $cacheKey = Str::random(40); + while (in_array($cacheKey, $this->use_cache_keys)) { + $cacheKey = Str::random(40); + } + + $viewData->cache_key = $cacheKey; + } + + return $this->cachePath.'/'.sha1($viewData->cache_key).'.php'; + } + + /** + * Determine if the view at the given path is expired. + * + * @param object $viewData + * @return bool + */ + public function isExpired($viewData) + { + + // adds ability to force template recompile + if ($this->forceTemplateRecompile) { + return true; + } + + $compiled = $this->getCompiledPath($viewData); + + // If the compiled file doesn't exist we will indicate that the view is expired + // so that it can be re-compiled. Else, we will verify the last modification + // of the views is less than the modification times of the compiled views. + if ( ! $this->cachePath || ! $this->files->exists($compiled)) + { return true; } + // If set to 0, then return cache has expired + if (property_exists($viewData, 'secondsTemplateCacheExpires')) { + if ($viewData->secondsTemplateCacheExpires == 0) { + return true; + } + } else { + $viewData->secondsTemplateCacheExpires = 0; + } + // Note: The lastModified time for a file on homestead will use the time from the host system. // This means the vm time could be off, so setting the timeout to seconds may not work as expected. return time() >= ($this->files->lastModified($compiled) + $viewData->secondsTemplateCacheExpires) ; - } + } + } diff --git a/src/Engines/CompilerEngine.php b/src/Engines/CompilerEngine.php index 14c1e63..106ae6b 100644 --- a/src/Engines/CompilerEngine.php +++ b/src/Engines/CompilerEngine.php @@ -44,8 +44,6 @@ public function get($path, array $data = []) // which have been rendered for right exception messages to be generated. $results = $this->evaluatePath($compiled, $data); - - array_pop($this->lastCompiled); return $results; @@ -54,6 +52,8 @@ public function get($path, array $data = []) /** * Set the delete view cache after render flag. * + * @deprecated I can't seem to find when the setting was actually used. If you want this, submit a bug and I will see about adding the ability. + * * @param bool $delete */ public function setDeleteViewCacheAfterRender($delete = true) { diff --git a/src/Facade/StringBlade.php b/src/Facade/StringBlade.php new file mode 100644 index 0000000..bf8ca37 --- /dev/null +++ b/src/Facade/StringBlade.php @@ -0,0 +1,32 @@ +getEngineResolver()->resolve('stringblade')->getCompiler(); + //return static::$app['view']->getEngineResolver()->resolve('stringblade')->getCompiler(); + } +} diff --git a/src/Facades/StringBlade.php b/src/Facades/StringBlade.php deleted file mode 100644 index 04cae1a..0000000 --- a/src/Facades/StringBlade.php +++ /dev/null @@ -1,21 +0,0 @@ -getEngineResolver()->resolve('stringblade')->getCompiler(); - } -} diff --git a/src/Factory.php b/src/Factory.php index 0744164..92977fc 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -1,25 +1,14 @@ finder = $finder; - $this->events = $events; - $this->engines = $engines; - - $this->share('__env', $this); - } - - /** - * Get the evaluated view contents for the given view. - * - * @param string $path - * @param array $data - * @param array $mergeData - * @return \Illuminate\Contracts\View\View - */ - public function file($path, $data = [], $mergeData = []) - { - $data = array_merge($mergeData, $this->parseData($data)); - - return tap($this->viewInstance($path, $path, $data), function ($view) { - $this->callCreator($view); - }); - } - /** * Get the evaluated view contents for the given view. * @@ -129,7 +84,9 @@ public function make($view, $data = [], $mergeData = []) // For string rendering if (is_array($view)) { - return $this->makeStringView($view, $data); + return tap($this->stringViewInstance($view, $data), function ($view) { + $this->callCreator($view); + }); } $path = $this->finder->find( @@ -145,326 +102,28 @@ public function make($view, $data = [], $mergeData = []) }); } - /** - * Get the evaluated string view contents. - * - * @param array $view - * @param array $data - * @return \Wpb\String_Blade_Compiler\StringView - */ - protected function makeStringView(array $view, $data=[]) - { - return tap($this->stringViewInstance($view, $data), function ($view) { - $this->callCreator($view); - }); - } - - /** - * Get the first view that actually exists from the given list. - * - * @param array $views - * @param array $data - * @param array $mergeData - * @return \Illuminate\Contracts\View\View|\Wpb\String_Blade_Compiler\StringView - */ - public function first(array $views, $data = [], $mergeData = []) - { - $view = collect($views)->first(function ($view) { - return $this->exists($view); - }); - - if (! $view) { - throw new InvalidArgumentException('None of the views in the given array exist.'); - } - - return $this->make($view, $data, $mergeData); - } - - /** - * Get the rendered content of the view based on a given condition. - * - * @param bool $condition - * @param string $view - * @param array $data - * @param array $mergeData - * @return string - */ - public function renderWhen($condition, $view, $data = [], $mergeData = []) - { - if (! $condition) { - return ''; - } - - return $this->make($view, $this->parseData($data), $mergeData)->render(); - } - - /** - * Get the rendered contents of a partial from a loop. - * - * @param string $view - * @param array $data - * @param string $iterator - * @param string $empty - * @return string - */ - public function renderEach($view, $data, $iterator, $empty = 'raw|') - { - $result = ''; - - // If is actually data in the array, we will loop through the data and append - // an instance of the partial view to the final result HTML passing in the - // iterated value of this data array, allowing the views to access them. - if (count($data) > 0) { - foreach ($data as $key => $value) { - $result .= $this->make( - $view, ['key' => $key, $iterator => $value] - )->render(); - } - } - - // If there is no data in the array, we will render the contents of the empty - // view. Alternatively, the "empty view" could be a raw string that begins - // with "raw|" for convenience and to let this know that it is a string. - else { - $result = Str::startsWith($empty, 'raw|') - ? substr($empty, 4) - : $this->make($empty)->render(); - } - - return $result; - } - - /** - * Normalize a view name. - * - * @param string $name - * @return string - */ - protected function normalizeName($name) - { - return ViewName::normalize($name); - } - - /** - * Parse the given data into a raw array. - * - * @param mixed $data - * @return array - */ - protected function parseData($data) - { - return $data instanceof Arrayable ? $data->toArray() : $data; - } - - /** - * Create a new view instance from the given arguments. - * - * @param string $view - * @param string $path - * @param array $data - * @return \Illuminate\Contracts\View\View - */ - protected function viewInstance($view, $path, $data) - { - return new View($this, $this->getEngineFromPath($path), $view, $path, $data); - } - /** * Create a new string view instance from the given arguments. * - * @param string $view + * @param string|array $view * @param array $data - * @return \Wpb\String_Blade_Compiler\StringView + * @return StringView */ protected function stringViewInstance($view, $data) { - return new StringView($this, $this->engines->resolve('stringblade'), $view, 'not-used', $data); - } - - /** - * Determine if a given view exists. - * - * @param string $view - * @return bool - */ - public function exists($view) - { - try { - $this->finder->find($view); - } catch (InvalidArgumentException $e) { - return false; - } - - return true; - } - - /** - * Get the appropriate view engine for the given path. - * - * @param string $path - * @return \Illuminate\Contracts\View\Engine - * - * @throws \InvalidArgumentException - */ - public function getEngineFromPath($path) - { - if (! $extension = $this->getExtension($path)) { - throw new InvalidArgumentException("Unrecognized extension in file: $path"); - } - - $engine = $this->extensions[$extension]; - - return $this->engines->resolve($engine); - } - - /** - * Get the appropriate view engine for the given string key. - * - * @param string $stringkey - * @return \Illuminate\Contracts\View\Engine - * - * @throws \InvalidArgumentException - */ - public function getEngineFromStringKey($stringkey) - { - return $this->engines->resolve($stringkey); - } - - /** - * Get the extension used by the view file. - * - * @param string $path - * @return string - */ - protected function getExtension($path) - { - $extensions = array_keys($this->extensions); - - return Arr::first($extensions, function ($value) use ($path) { - return Str::endsWith($path, '.'.$value); - }); - } - - /** - * Add a piece of shared data to the environment. - * - * @param array|string $key - * @param mixed $value - * @return mixed - */ - public function share($key, $value = null) - { - $keys = is_array($key) ? $key : [$key => $value]; - - foreach ($keys as $key => $value) { - $this->shared[$key] = $value; - } - - return $value; - } - - /** - * Increment the rendering counter. - * - * @return void - */ - public function incrementRender() - { - $this->renderCount++; - } - - /** - * Decrement the rendering counter. - * - * @return void - */ - public function decrementRender() - { - $this->renderCount--; - } - - /** - * Check if there are no active render operations. - * - * @return bool - */ - public function doneRendering() - { - return $this->renderCount == 0; - } - - /** - * Add a location to the array of view locations. - * - * @param string $location - * @return void - */ - public function addLocation($location) - { - $this->finder->addLocation($location); - } - - /** - * Add a new namespace to the loader. - * - * @param string $namespace - * @param string|array $hints - * @return $this - */ - public function addNamespace($namespace, $hints) - { - $this->finder->addNamespace($namespace, $hints); - - return $this; + return new StringView($this, $this->engines->resolve('stringblade'), $view, null, $data); } /** - * Prepend a new namespace to the loader. - * - * @param string $namespace - * @param string|array $hints - * @return $this - */ - public function prependNamespace($namespace, $hints) - { - $this->finder->prependNamespace($namespace, $hints); - - return $this; - } - - /** - * Replace the namespace hints for the given namespace. - * - * @param string $namespace - * @param string|array $hints - * @return $this - */ - public function replaceNamespace($namespace, $hints) - { - $this->finder->replaceNamespace($namespace, $hints); - - return $this; - } - - /** - * Register a valid view extension and its engine. + * Flush all of the section contents if done rendering. * - * @param string $extension - * @param string $engine - * @param \Closure $resolver * @return void */ - public function addExtension($extension, $engine, $resolver = null) + public function flushStateIfDoneRendering() { - $this->finder->addExtension($extension); - - if (isset($resolver)) { - $this->engines->register($engine, $resolver); + if ($this->doneRendering()) { + $this->flushState(); } - - unset($this->extensions[$extension]); - - $this->extensions = array_merge([$extension => $engine], $this->extensions); } /** @@ -481,129 +140,19 @@ public function flushState() } /** - * Flush all of the section contents if done rendering. - * - * @return void - */ - public function flushStateIfDoneRendering() - { - if ($this->doneRendering()) { - $this->flushState(); - } - } - - /** - * Get the extension to engine bindings. - * - * @return array - */ - public function getExtensions() - { - return $this->extensions; - } - - /** - * Get the engine resolver instance. - * - * @return \Illuminate\View\Engines\EngineResolver - */ - public function getEngineResolver() - { - return $this->engines; - } - - /** - * Get the view finder instance. - * - * @return \Wpb\String_Blade_Compiler\ViewFinderInterface - */ - public function getFinder() - { - return $this->finder; - } - - /** - * Set the view finder instance. - * - * @param \Wpb\String_Blade_Compiler\ViewFinderInterface $finder - * @return void - */ - public function setFinder(ViewFinderInterface $finder) - { - $this->finder = $finder; - } - - /** - * Flush the cache of views located by the finder. - * - * @return void - */ - public function flushFinderCache() - { - $this->getFinder()->flush(); - } - - /** - * Get the event dispatcher instance. - * - * @return \Illuminate\Contracts\Events\Dispatcher - */ - public function getDispatcher() - { - return $this->events; - } - - /** - * Set the event dispatcher instance. - * - * @param \Illuminate\Contracts\Events\Dispatcher $events - * @return void - */ - public function setDispatcher(Dispatcher $events) - { - $this->events = $events; - } - - /** - * Get the IoC container instance. + * Get the appropriate view engine for the given string key. * - * @return \Illuminate\Contracts\Container\Container - */ - public function getContainer() - { - return $this->container; - } - - /** - * Set the IoC container instance. + * @param string $stringkey + * @return \Illuminate\Contracts\View\Engine * - * @param \Illuminate\Contracts\Container\Container $container - * @return void - */ - public function setContainer(Container $container) - { - $this->container = $container; - } - - /** - * Get an item from the shared data. + * ['file', 'php', 'blade', 'stringblade'] in StringBladeServiceProvider:registerEngineResolver * - * @param string $key - * @param mixed $default - * @return mixed + * @throws \InvalidArgumentException */ - public function shared($key, $default = null) + public function getEngineFromStringKey($stringkey) { - return Arr::get($this->shared, $key, $default); + // resolve function throws error if $stringkey is not a registered engine + return $this->engines->resolve($stringkey); } - /** - * Get all of the shared data for the environment. - * - * @return array - */ - public function getShared() - { - return $this->shared; - } } diff --git a/src/FileViewFinder.php b/src/FileViewFinder.php deleted file mode 100644 index 32e8f43..0000000 --- a/src/FileViewFinder.php +++ /dev/null @@ -1,299 +0,0 @@ -files = $files; - $this->paths = $paths; - - if (isset($extensions)) { - $this->extensions = $extensions; - } - } - - /** - * Get the fully qualified location of the view. - * - * @param string $name - * @return string - */ - public function find($name) - { - if (isset($this->views[$name])) { - return $this->views[$name]; - } - - if ($this->hasHintInformation($name = trim($name))) { - return $this->views[$name] = $this->findNamedPathView($name); - } - - return $this->views[$name] = $this->findInPaths($name, $this->paths); - } - - /** - * Get the path to a template with a named path. - * - * @param string $name - * @return string - */ - protected function findNamedPathView($name) - { - list($namespace, $view) = $this->getNamespaceSegments($name); - - return $this->findInPaths($view, $this->hints[$namespace]); - } - - /** - * Get the segments of a template with a named path. - * - * @param string $name - * @return array - * - * @throws \InvalidArgumentException - */ - protected function getNamespaceSegments($name) - { - $segments = explode(static::HINT_PATH_DELIMITER, $name); - - if (count($segments) != 2) { - throw new InvalidArgumentException("View [$name] has an invalid name."); - } - - if (!isset($this->hints[$segments[0]])) { - throw new InvalidArgumentException("No hint path defined for [{$segments[0]}]."); - } - - return $segments; - } - - /** - * Find the given view in the list of paths. - * - * @param string $name - * @param array $paths - * @return string - * - * @throws \InvalidArgumentException - */ - protected function findInPaths($name, $paths) - { - foreach ((array) $paths as $path) { - foreach ($this->getPossibleViewFiles($name) as $file) { - if ($this->files->exists($viewPath = $path.'/'.$file)) { - return $viewPath; - } - } - } - - throw new InvalidArgumentException("View [$name] not found."); - } - - /** - * Get an array of possible view files. - * - * @param string $name - * @return array - */ - protected function getPossibleViewFiles($name) - { - return array_map(function ($extension) use ($name) { - return str_replace('.', '/', $name).'.'.$extension; - - }, $this->extensions); - } - - /** - * Prepend a location to the finder. - * - * @param string $location - * @return void - */ - public function prependLocation($location) - { - array_unshift($this->paths, $location); - } - - /** - * Add a location to the finder. - * - * @param string $location - * @return void - */ - public function addLocation($location) - { - $this->paths[] = $location; - } - - /** - * Add a namespace hint to the finder. - * - * @param string $namespace - * @param string|array $hints - * @return void - */ - public function addNamespace($namespace, $hints) - { - $hints = (array) $hints; - - if (isset($this->hints[$namespace])) { - $hints = array_merge($this->hints[$namespace], $hints); - } - - $this->hints[$namespace] = $hints; - } - - /** - * Replace the namespace hints for the given namespace. - * - * @param string $namespace - * @param string|array $hints - * @return void - */ - public function replaceNamespace($namespace, $hints) - { - $this->hints[$namespace] = (array) $hints; - } - - /** - * Prepend a namespace hint to the finder. - * - * @param string $namespace - * @param string|array $hints - * @return void - */ - public function prependNamespace($namespace, $hints) - { - $hints = (array) $hints; - - if (isset($this->hints[$namespace])) { - $hints = array_merge($hints, $this->hints[$namespace]); - } - - $this->hints[$namespace] = $hints; - } - - /** - * Register an extension with the view finder. - * - * @param string $extension - * @return void - */ - public function addExtension($extension) - { - if (($index = array_search($extension, $this->extensions)) !== false) { - unset($this->extensions[$index]); - } - - array_unshift($this->extensions, $extension); - } - - /** - * Returns whether or not the view specify a hint information. - * - * @param string $name - * @return bool - */ - public function hasHintInformation($name) - { - return strpos($name, static::HINT_PATH_DELIMITER) > 0; - } - - /** - * Flush the cache of located views. - * - * @return void - */ - public function flush() - { - $this->views = []; - } - - /** - * Get the filesystem instance. - * - * @return \Illuminate\Filesystem\Filesystem - */ - public function getFilesystem() - { - return $this->files; - } - - /** - * Get the active view paths. - * - * @return array - */ - public function getPaths() - { - return $this->paths; - } - - /** - * Get the namespace to file path hints. - * - * @return array - */ - public function getHints() - { - return $this->hints; - } - - /** - * Get registered extensions. - * - * @return array - */ - public function getExtensions() - { - return $this->extensions; - } -} diff --git a/src/ViewServiceProvider.php b/src/StringBladeServiceProvider.php similarity index 52% rename from src/ViewServiceProvider.php rename to src/StringBladeServiceProvider.php index e2a6428..77e1168 100644 --- a/src/ViewServiceProvider.php +++ b/src/StringBladeServiceProvider.php @@ -1,15 +1,13 @@ mergeConfigFrom( - __DIR__.'/config/blade.php', 'blade' + __DIR__.'/../config/blade.php', 'blade' ); - $this->app->booting(function () { - $loader = \Illuminate\Foundation\AliasLoader::getInstance(); - $loader->alias('StringBlade', 'Wpb\String_Blade_Compiler\Facades\StringBlade'); - }); + // load the alias (handled by the Laravel autoloader) + //$this->app->alias('StringBlade', 'Wpb\String_Blade_Compiler\Facades\StringBlade'); + + $this->registerEngineResolver(); - // continue with parent register - parent::register(); + $this->registerViewFinder(); + + $this->registerFactory(); } /** - * Register the engine resolver instance. + * Register the view environment. * * @return void */ - public function registerEngineResolver() + public function registerFactory() { - $this->app->singleton('view.engine.resolver', function () { - $resolver = new EngineResolver; + $this->app->singleton('view', function ($app) { + // Next we need to grab the engine resolver instance that will be used by the + // environment. The resolver will be used by an environment to get each of + // the various engine implementations such as plain PHP or Blade engine. + $resolver = $app['view.engine.resolver']; - // Next we will register the various engines with the resolver so that the - // environment can resolve the engines it needs for various views based - // on the extension of view files. We call a method for each engines. - foreach (['file', 'php', 'blade', 'StringBlade'] as $engine) { - $this->{'register'.ucfirst($engine).'Engine'}($resolver); - } + $finder = $app['view.finder']; - return $resolver; + $factory = $this->createFactory($resolver, $finder, $app['events']); + + // We will also set the container instance on this view environment since the + // view composers may be classes registered in the container, which allows + // for great testable, flexible composers for the application developer. + $factory->setContainer($app); + + $factory->share('app', $app); + + return $factory; }); } /** - * Register the file engine implementation. + * Create a new Factory Instance. * * @param \Illuminate\View\Engines\EngineResolver $resolver - * @return void + * @param \Illuminate\View\ViewFinderInterface $finder + * @param \Illuminate\Contracts\Events\Dispatcher $events + * @return \Illuminate\View\Factory */ - public function registerFileEngine($resolver) + protected function createFactory($resolver, $finder, $events) { - $resolver->register('file', function () { - return new FileEngine; - }); + return new Factory($resolver, $finder, $events); } /** - * Register the StringBlade engine implementation. + * Register the view finder implementation. * - * @param \Illuminate\View\Engines\EngineResolver $resolver * @return void */ - public function registerStringBladeEngine($resolver) + public function registerViewFinder() { - $app = $this->app; + // since view.find should be registered, lets get the paths and hints - in case they have changed + $oldFinder = []; + if ($this->app->resolved('view.finder')) { + $oldFinder['paths'] = $this->app['view']->getFinder()->getPaths(); + $oldFinder['hints'] = $this->app['view']->getFinder()->getHints(); + } - // The Compiler engine requires an instance of the CompilerInterface, which in - // this case will be the Blade compiler, so we'll first create the compiler - // instance to pass into the engine so it can compile the views properly. - $app->singleton('stringblade.compiler', function ($app) { - $cache = $app['config']['view.compiled']; - return new StringBladeCompiler($app['files'], $cache); - }); + // recreate the view.finder + $this->app->bind('view.finder', function ($app) use ($oldFinder) { - $resolver->register('stringblade', function () use ($app) { - return new CompilerEngine($app['stringblade.compiler'], $app['files']); + $paths = (isset($oldFinder['paths']))?array_unique(array_merge($app['config']['view.paths'], $oldFinder['paths']), SORT_REGULAR):$app['config']['view.paths']; + + $viewFinder = new FileViewFinder($app['files'], $paths); + + if (!empty($oldFinder['hints'])) { + array_walk($oldFinder['hints'], function($value, $key) use ($viewFinder) { + $viewFinder->addNamespace($key, $value); + }); + } + + return $viewFinder; }); } - /** - * Perform post-registration booting of services. + * Register the engine resolver instance. * + * @return void */ - public function boot() + public function registerEngineResolver() { - // setup publishing of config - $this->publishes([ - __DIR__.'/config/blade.php' => config_path('blade.php'), - ], 'config'); + // recreate the resolver, adding stringblade + $this->app->singleton('view.engine.resolver', function () { + $resolver = new EngineResolver; + + // Next, we will register the various view engines with the resolver so that the + // environment will resolve the engines needed for various views based on the + // extension of view file. We call a method for each of the view's engines. + foreach (['file', 'php', 'blade', 'stringblade'] as $engine) { + $this->{'register'.ucfirst($engine).'Engine'}($resolver); + } + + return $resolver; + }); } /** - * Register the Blade engine implementation. + * Register the StringBlade engine implementation. * * @param \Illuminate\View\Engines\EngineResolver $resolver * @return void */ - public function registerBladeEngine($resolver) + public function registerStringBladeEngine($resolver) { $app = $this->app; // The Compiler engine requires an instance of the CompilerInterface, which in // this case will be the Blade compiler, so we'll first create the compiler // instance to pass into the engine so it can compile the views properly. - $app->singleton('blade.compiler', function ($app) { + $app->singleton('stringblade.compiler', function ($app) { $cache = $app['config']['view.compiled']; - return new BladeCompiler($app['files'], $cache); - }); - - $resolver->register('blade', function () use ($app) { - return new CompilerEngine($app['blade.compiler'], $app['files']); - }); - } - - /** - * Register the view finder implementation. - * - * @return void - */ - public function registerViewFinder() - { - $this->app->bind('view.finder', function ($app) { - $paths = $app['config']['view.paths']; - return new FileViewFinder($app['files'], $paths); + return new StringBladeCompiler($app['files'], $cache); }); - } - - /** - * Register the view environment. - * - * @return void - */ - public function registerFactory() - { - $this->app->singleton('view', function ($app) { - // Next we need to grab the engine resolver instance that will be used by the - // environment. The resolver will be used by an environment to get each of - // the various engine implementations such as plain PHP or Blade engine. - $resolver = $app['view.engine.resolver']; - - $finder = $app['view.finder']; - - $env = new Factory($resolver, $finder, $app['events']); - // We will also set the container instance on this view environment since the - // view composers may be classes registered in the container, which allows - // for great testable, flexible composers for the application developer. - $env->setContainer($app); - - $env->share('app', $app); - - return $env; + $resolver->register('stringblade', function () use ($app) { + return new CompilerEngine($app['stringblade.compiler']); }); } -} +} \ No newline at end of file diff --git a/src/StringView.php b/src/StringView.php index 197d7ef..ea17e21 100644 --- a/src/StringView.php +++ b/src/StringView.php @@ -2,20 +2,26 @@ namespace Wpb\String_Blade_Compiler; -use App, ArrayAccess; -use Config; -use Illuminate\Contracts\View\Engine; +use Exception; +use Throwable; +use ArrayAccess; +use BadMethodCallException; +use Illuminate\Support\Str; +use Illuminate\Support\MessageBag; +use Illuminate\Contracts\Support\Arrayable; +use Illuminate\Contracts\Support\Renderable; +use Illuminate\Contracts\Support\MessageProvider; use Illuminate\Contracts\View\View as ViewContract; +use Illuminate\Contracts\View\Engine as Engine; -class StringView extends View implements ArrayAccess, ViewContract { - - protected $template_field = 'template'; +class StringView extends View +{ /** * Create a new view instance. * - * @param \Wpb\String_Blade_Compiler\Factory $factory - * @param \Illuminate\Contracts\View\Engine $engine + * @param Factory $factory + * @param Engine $engine * @param string $view * @param string $path * @param array $data @@ -23,20 +29,12 @@ class StringView extends View implements ArrayAccess, ViewContract { */ public function __construct(Factory $factory, Engine $engine, $view, $path, $data = []) { - // setup variables $this->view = (is_array($view))?(object) $view:$view; - $this->path = $this->view; + $this->path = $path; $this->engine = $engine; $this->factory = $factory; - $this->data = $this->parseData($data); - - //$this->data = $data instanceof Arrayable ? $data->toArray() : (array) $data; - - // if ($data instanceof Arrayable) { - // var_dump($this->data); - //} - + $this->data = $data instanceof Arrayable ? $data->toArray() : (array) $data; // check if view has secondsTemplateCacheExpires set, or get from config if ( !property_exists($this->view, "secondsTemplateCacheExpires") || !is_numeric($this->view->secondsTemplateCacheExpires) ) { @@ -63,7 +61,11 @@ public function __construct(Factory $factory, Engine $engine, $view, $path, $dat $this->view->cache_key = md5($this->view->template); } } - } + } + + public function getViewTemplate() { + return $this->view->template; + } /** * Get the name of the view. @@ -76,76 +78,46 @@ public function getName() } /** - * Get the array of view data. + * Get the evaluated contents of the view. * - * @return array + * @return string */ - public function getData() + protected function getContents() { - return $this->data; - } - /** - * Get a evaluated view contents for the given view. - * - * @param object $view - * @param array $data - * @param array $mergeData - * @return \Illuminate\View\View - * @throws \Exception - */ - public function make($view, $data = array(), $mergeData = array()) - { - $this->path = $view; - $this->data = array_merge($mergeData, $this->parseData($data)); - return $this; - } - /** - * Get the evaluated contents of the view. - * - * @return string - */ - protected function getContents() - { /** - * This property will be added to models being compiled with StringView - * to keep track of which field in the model is being compiled - */ - $this->path->__string_blade_compiler_template_field = $this->template_field; + * This property will be added to models being compiled with StringView + * to keep track of which field in the model is being compiled + */ + //$this->path->__string_blade_compiler_template_field = $this->template_field; + + if (is_null($this->path)) { + return $this->engine->get($this->view, $this->gatherData()); + } return $this->engine->get($this->path, $this->gatherData()); - //return parent::getContents(); - } - - /** - * Parse the given data into a raw array. - * - * @param mixed $data - * @return array - */ - protected function parseData($data) - { - return $data instanceof Arrayable ? $data->toArray() : $data; - } - - /** - * Checks if a string is a valid timestamp. - * from https://gist.github.com/sepehr/6351385 - * - * @param string $timestamp Timestamp to validate. - * - * @return bool - */ + //return parent::getContents(); + } + + /** + * Checks if a string is a valid timestamp. + * from https://gist.github.com/sepehr/6351385 + * + * @param string $timestamp Timestamp to validate. + * + * @return bool + */ function is_timestamp($timestamp) { $check = (is_int($timestamp) OR is_float($timestamp)) - ? $timestamp - : (string) (int) $timestamp; + ? $timestamp + : (string) (int) $timestamp; return ($check === $timestamp) - AND ( (int) $timestamp <= PHP_INT_MAX) - AND ( (int) $timestamp >= ~PHP_INT_MAX); + AND ( (int) $timestamp <= PHP_INT_MAX) + AND ( (int) $timestamp >= ~PHP_INT_MAX); } -} + +} \ No newline at end of file diff --git a/src/View.php b/src/View.php index 77ed97c..847a583 100644 --- a/src/View.php +++ b/src/View.php @@ -9,7 +9,6 @@ use Illuminate\Support\Str; use Illuminate\Support\MessageBag; use Illuminate\Contracts\Support\Arrayable; -use Illuminate\View\Engines\EngineInterface; use Illuminate\Contracts\Support\Renderable; use Illuminate\Contracts\Support\MessageProvider; use Illuminate\Contracts\View\View as ViewContract; @@ -22,7 +21,7 @@ class View extends \Illuminate\View\View * Create a new view instance. * * @param \Wpb\String_Blade_Compiler\Factory $factory - * @param \Illuminate\View\Engines\EngineInterface $engine + * @param \Illuminate\Contracts\View\Engine $engine * @param string $view * @param string $path * @param array $data @@ -36,7 +35,21 @@ public function __construct(Factory $factory, Engine $engine, $view, $path, $dat $this->factory = $factory; $this->data = $data instanceof Arrayable ? $data->toArray() : (array) $data; - + + } + + /** + * Dynamically bind parameters to the view. + * + * @param string $method + * @param array $parameters + * @return \Illuminate\View\View + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + return parent::__call($method, $parameters); } } diff --git a/src/ViewFinderInterface.php b/src/ViewFinderInterface.php deleted file mode 100644 index bc951c4..0000000 --- a/src/ViewFinderInterface.php +++ /dev/null @@ -1,64 +0,0 @@ -compiler = new BladeCompiler(m::mock(Filesystem::class), __DIR__); + parent::setUp(); + } + + protected function tearDown(): void + { + m::close(); + + parent::tearDown(); + } +} diff --git a/tests/View/Blade/BladeAppendTest.php b/tests/View/Blade/BladeAppendTest.php new file mode 100644 index 0000000..88e5926 --- /dev/null +++ b/tests/View/Blade/BladeAppendTest.php @@ -0,0 +1,11 @@ +assertEquals('appendSection(); ?>', $this->compiler->compileString('@append')); + } +} diff --git a/tests/View/Blade/BladeBreakStatementsTest.php b/tests/View/Blade/BladeBreakStatementsTest.php new file mode 100644 index 0000000..cd12290 --- /dev/null +++ b/tests/View/Blade/BladeBreakStatementsTest.php @@ -0,0 +1,71 @@ + +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testBreakStatementsWithExpressionAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +test +@break(TRUE) +@endfor'; + $expected = ' +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testBreakStatementsWithArgumentAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +test +@break(2) +@endfor'; + $expected = ' +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testBreakStatementsWithSpacedArgumentAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +test +@break( 2 ) +@endfor'; + $expected = ' +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testBreakStatementsWithFaultyArgumentAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +test +@break(-2) +@endfor'; + $expected = ' +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeCanStatementsTest.php b/tests/View/Blade/BladeCanStatementsTest.php new file mode 100644 index 0000000..570d367 --- /dev/null +++ b/tests/View/Blade/BladeCanStatementsTest.php @@ -0,0 +1,21 @@ +check(\'update\', [$post])): ?> +breeze +check(\'delete\', [$post])): ?> +sneeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeCananyStatementsTest.php b/tests/View/Blade/BladeCananyStatementsTest.php new file mode 100644 index 0000000..527145a --- /dev/null +++ b/tests/View/Blade/BladeCananyStatementsTest.php @@ -0,0 +1,21 @@ +any([\'create\', \'update\'], [$post])): ?> +breeze +any([\'delete\', \'approve\'], [$post])): ?> +sneeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeCannotStatementsTest.php b/tests/View/Blade/BladeCannotStatementsTest.php new file mode 100644 index 0000000..e545fb6 --- /dev/null +++ b/tests/View/Blade/BladeCannotStatementsTest.php @@ -0,0 +1,21 @@ +denies(\'update\', [$post])): ?> +breeze +denies(\'delete\', [$post])): ?> +sneeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeCommentsTest.php b/tests/View/Blade/BladeCommentsTest.php new file mode 100644 index 0000000..adb45bf --- /dev/null +++ b/tests/View/Blade/BladeCommentsTest.php @@ -0,0 +1,27 @@ +assertEmpty($this->compiler->compileString($string)); + + $string = '{{-- +this is a comment +--}}'; + $this->assertEmpty($this->compiler->compileString($string)); + + $string = sprintf('{{-- this is an %s long comment --}}', str_repeat('extremely ', 1000)); + $this->assertEmpty($this->compiler->compileString($string)); + } + + public function testBladeCodeInsideCommentsIsNotCompiled() + { + $string = '{{-- @foreach() --}}'; + + $this->assertEmpty($this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeContinueStatementsTest.php b/tests/View/Blade/BladeContinueStatementsTest.php new file mode 100644 index 0000000..fa0616d --- /dev/null +++ b/tests/View/Blade/BladeContinueStatementsTest.php @@ -0,0 +1,71 @@ + +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testContinueStatementsWithExpressionAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +test +@continue(TRUE) +@endfor'; + $expected = ' +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testContinueStatementsWithArgumentAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +test +@continue(2) +@endfor'; + $expected = ' +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testContinueStatementsWithSpacedArgumentAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +test +@continue( 2 ) +@endfor'; + $expected = ' +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testContinueStatementsWithFaultyArgumentAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +test +@continue(-2) +@endfor'; + $expected = ' +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeCustomTest.php b/tests/View/Blade/BladeCustomTest.php new file mode 100644 index 0000000..b32dedb --- /dev/null +++ b/tests/View/Blade/BladeCustomTest.php @@ -0,0 +1,216 @@ +assertEquals(' ', $this->compiler->compileString("@if(\$test) @endif")); + } + + public function testMixingYieldAndEcho() + { + $this->assertEquals('yieldContent(\'title\'); ?> - ', $this->compiler->compileString("@yield('title') - {{Config::get('site.title')}}")); + } + + public function testCustomExtensionsAreCompiled() + { + $this->compiler->extend(function ($value) { + return str_replace('foo', 'bar', $value); + }); + $this->assertEquals('bar', $this->compiler->compileString('foo')); + } + + public function testCustomStatements() + { + $this->assertCount(0, $this->compiler->getCustomDirectives()); + $this->compiler->directive('customControl', function ($expression) { + return ""; + }); + $this->assertCount(1, $this->compiler->getCustomDirectives()); + + $string = '@if($foo) +@customControl(10, $foo, \'bar\') +@endif'; + $expected = ' + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomShortStatements() + { + $this->compiler->directive('customControl', function ($expression) { + return ''; + }); + + $string = '@customControl'; + $expected = ''; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testValidCustomNames() + { + $this->assertNull($this->compiler->directive('custom', function () { + })); + $this->assertNull($this->compiler->directive('custom_custom', function () { + })); + $this->assertNull($this->compiler->directive('customCustom', function () { + })); + $this->assertNull($this->compiler->directive('custom::custom', function () { + })); + } + + public function testInvalidCustomNames() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The directive name [custom-custom] is not valid.'); + $this->compiler->directive('custom-custom', function () { + }); + } + + public function testInvalidCustomNames2() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The directive name [custom:custom] is not valid.'); + $this->compiler->directive('custom:custom', function () { + }); + } + + public function testCustomExtensionOverwritesCore() + { + $this->compiler->directive('foreach', function ($expression) { + return ''; + }); + + $string = '@foreach'; + $expected = ''; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomConditions() + { + $this->compiler->if('custom', function ($user) { + return true; + }); + + $string = '@custom($user) +@endcustom'; + $expected = ' +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomIfElseConditions() + { + $this->compiler->if('custom', function ($anything) { + return true; + }); + + $string = '@custom($user) +@elsecustom($product) +@else +@endcustom'; + $expected = ' + + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomConditionsAccepts0AsArgument() + { + $this->compiler->if('custom', function ($number) { + return true; + }); + + $string = '@custom(0) +@elsecustom(0) +@endcustom'; + $expected = ' + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomComponents() + { + $this->compiler->component('app.components.alert', 'alert'); + + $string = '@alert +@endalert'; + $expected = 'startComponent(\'app.components.alert\'); ?> +renderComponent(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomComponentsWithSlots() + { + $this->compiler->component('app.components.alert', 'alert'); + + $string = '@alert([\'type\' => \'danger\']) +@endalert'; + $expected = 'startComponent(\'app.components.alert\', [\'type\' => \'danger\']); ?> +renderComponent(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomComponentsDefaultAlias() + { + $this->compiler->component('app.components.alert'); + + $string = '@alert +@endalert'; + $expected = 'startComponent(\'app.components.alert\'); ?> +renderComponent(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomComponentsWithExistingDirective() + { + $this->compiler->component('app.components.foreach'); + + $string = '@foreach +@endforeach'; + $expected = 'startComponent(\'app.components.foreach\'); ?> +renderComponent(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomIncludes() + { + $this->compiler->include('app.includes.input', 'input'); + + $string = '@input'; + $expected = 'make(\'app.includes.input\', [], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomIncludesWithData() + { + $this->compiler->include('app.includes.input', 'input'); + + $string = '@input([\'type\' => \'email\'])'; + $expected = 'make(\'app.includes.input\', [\'type\' => \'email\'], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomIncludesDefaultAlias() + { + $this->compiler->include('app.includes.input'); + + $string = '@input'; + $expected = 'make(\'app.includes.input\', [], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testCustomIncludesWithExistingDirective() + { + $this->compiler->include('app.includes.foreach'); + + $string = '@foreach'; + $expected = 'make(\'app.includes.foreach\', [], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeEachTest.php b/tests/View/Blade/BladeEachTest.php new file mode 100644 index 0000000..6fbcbb6 --- /dev/null +++ b/tests/View/Blade/BladeEachTest.php @@ -0,0 +1,12 @@ +assertEquals('renderEach(\'foo\', \'bar\'); ?>', $this->compiler->compileString('@each(\'foo\', \'bar\')')); + $this->assertEquals('renderEach(name(foo)); ?>', $this->compiler->compileString('@each(name(foo))')); + } +} diff --git a/tests/View/Blade/BladeEchoTest.php b/tests/View/Blade/BladeEchoTest.php new file mode 100644 index 0000000..25a7798 --- /dev/null +++ b/tests/View/Blade/BladeEchoTest.php @@ -0,0 +1,66 @@ +assertEquals('', $this->compiler->compileString('{!!$name!!}')); + $this->assertEquals('', $this->compiler->compileString('{!! $name !!}')); + $this->assertEquals('', $this->compiler->compileString('{!! + $name + !!}')); + + $this->assertEquals('', $this->compiler->compileString('{{{$name}}}')); + $this->assertEquals('', $this->compiler->compileString('{{$name}}')); + $this->assertEquals('', $this->compiler->compileString('{{ $name }}')); + $this->assertEquals('', $this->compiler->compileString('{{ + $name + }}')); + $this->assertEquals("\n\n", $this->compiler->compileString("{{ \$name }}\n")); + $this->assertEquals("\r\n\r\n", $this->compiler->compileString("{{ \$name }}\r\n")); + $this->assertEquals("\n\n", $this->compiler->compileString("{{ \$name }}\n")); + $this->assertEquals("\r\n\r\n", $this->compiler->compileString("{{ \$name }}\r\n")); + + $this->assertEquals('', + $this->compiler->compileString('{{ "Hello world or foo" }}')); + $this->assertEquals('', + $this->compiler->compileString('{{"Hello world or foo"}}')); + $this->assertEquals('', $this->compiler->compileString('{{$foo + $or + $baz}}')); + $this->assertEquals('', $this->compiler->compileString('{{ + "Hello world or foo" + }}')); + + $this->assertEquals('', + $this->compiler->compileString('{{ \'Hello world or foo\' }}')); + $this->assertEquals('', + $this->compiler->compileString('{{\'Hello world or foo\'}}')); + $this->assertEquals('', $this->compiler->compileString('{{ + \'Hello world or foo\' + }}')); + + $this->assertEquals('', + $this->compiler->compileString('{{ myfunc(\'foo or bar\') }}')); + $this->assertEquals('', + $this->compiler->compileString('{{ myfunc("foo or bar") }}')); + $this->assertEquals('', + $this->compiler->compileString('{{ myfunc("$name or \'foo\'") }}')); + } + + public function testEscapedWithAtEchosAreCompiled() + { + $this->assertEquals('{{$name}}', $this->compiler->compileString('@{{$name}}')); + $this->assertEquals('{{ $name }}', $this->compiler->compileString('@{{ $name }}')); + $this->assertEquals('{{ + $name + }}', + $this->compiler->compileString('@{{ + $name + }}')); + $this->assertEquals('{{ $name }} + ', + $this->compiler->compileString('@{{ $name }} + ')); + } +} diff --git a/tests/View/Blade/BladeElseAuthStatementsTest.php b/tests/View/Blade/BladeElseAuthStatementsTest.php new file mode 100644 index 0000000..73b217a --- /dev/null +++ b/tests/View/Blade/BladeElseAuthStatementsTest.php @@ -0,0 +1,53 @@ +getFiles(), __DIR__); + $string = '@auth("api") +breeze +@elseauth("standard") +wheeze +@endauth'; + $expected = 'guard("api")->check()): ?> +breeze +guard("standard")->check()): ?> +wheeze +'; + $this->assertEquals($expected, $compiler->compileString($string)); + } + + public function testPlainElseAuthStatementsAreCompiled() + { + $compiler = new BladeCompiler($this->getFiles(), __DIR__); + $string = '@auth("api") +breeze +@elseauth +wheeze +@endauth'; + $expected = 'guard("api")->check()): ?> +breeze +guard()->check()): ?> +wheeze +'; + $this->assertEquals($expected, $compiler->compileString($string)); + } + + protected function getFiles() + { + return m::mock(Filesystem::class); + } +} diff --git a/tests/View/Blade/BladeElseGuestStatementsTest.php b/tests/View/Blade/BladeElseGuestStatementsTest.php new file mode 100644 index 0000000..bac4615 --- /dev/null +++ b/tests/View/Blade/BladeElseGuestStatementsTest.php @@ -0,0 +1,37 @@ +getFiles(), __DIR__); + $string = '@guest("api") +breeze +@elseguest("standard") +wheeze +@endguest'; + $expected = 'guard("api")->guest()): ?> +breeze +guard("standard")->guest()): ?> +wheeze +'; + $this->assertEquals($expected, $compiler->compileString($string)); + } + + protected function getFiles() + { + return m::mock(Filesystem::class); + } +} diff --git a/tests/View/Blade/BladeElseIfStatementsTest.php b/tests/View/Blade/BladeElseIfStatementsTest.php new file mode 100644 index 0000000..a30bbd3 --- /dev/null +++ b/tests/View/Blade/BladeElseIfStatementsTest.php @@ -0,0 +1,21 @@ + +breeze + +boom +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeElseStatementsTest.php b/tests/View/Blade/BladeElseStatementsTest.php new file mode 100644 index 0000000..27f3007 --- /dev/null +++ b/tests/View/Blade/BladeElseStatementsTest.php @@ -0,0 +1,36 @@ + +breeze + +boom +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testElseIfStatementsAreCompiled() + { + $string = '@if(name(foo(bar))) +breeze +@elseif(boom(breeze)) +boom +@endif'; + $expected = ' +breeze + +boom +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeEndSectionsTest.php b/tests/View/Blade/BladeEndSectionsTest.php new file mode 100644 index 0000000..9ea597a --- /dev/null +++ b/tests/View/Blade/BladeEndSectionsTest.php @@ -0,0 +1,11 @@ +assertEquals('stopSection(); ?>', $this->compiler->compileString('@endsection')); + } +} diff --git a/tests/View/Blade/BladeEscapedTest.php b/tests/View/Blade/BladeEscapedTest.php new file mode 100644 index 0000000..e3c90cb --- /dev/null +++ b/tests/View/Blade/BladeEscapedTest.php @@ -0,0 +1,19 @@ +assertEquals('@foreach', $this->compiler->compileString('@@foreach')); + $this->assertEquals('@verbatim @continue @endverbatim', $this->compiler->compileString('@@verbatim @@continue @@endverbatim')); + $this->assertEquals('@foreach($i as $x)', $this->compiler->compileString('@@foreach($i as $x)')); + $this->assertEquals('@continue @break', $this->compiler->compileString('@@continue @@break')); + $this->assertEquals('@foreach( + $i as $x + )', $this->compiler->compileString('@@foreach( + $i as $x + )')); + } +} diff --git a/tests/View/Blade/BladeExpressionTest.php b/tests/View/Blade/BladeExpressionTest.php new file mode 100644 index 0000000..9bd436f --- /dev/null +++ b/tests/View/Blade/BladeExpressionTest.php @@ -0,0 +1,18 @@ +assertEquals('getFromJson(foo(bar(baz(qux(breeze()))))); ?> space () getFromJson(foo(bar)); ?>', $this->compiler->compileString('@lang(foo(bar(baz(qux(breeze()))))) space () @lang(foo(bar))')); + } + + public function testExpressionWithinHTML() + { + $this->assertEquals('>', $this->compiler->compileString('')); + $this->assertEquals('>', $this->compiler->compileString('')); + $this->assertEquals(' getFromJson(\'foo\'); ?>>', $this->compiler->compileString('')); + } +} diff --git a/tests/View/Blade/BladeExtendsTest.php b/tests/View/Blade/BladeExtendsTest.php new file mode 100644 index 0000000..83ca069 --- /dev/null +++ b/tests/View/Blade/BladeExtendsTest.php @@ -0,0 +1,31 @@ +make(\'foo\', \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = '@extends(name(foo))'.PHP_EOL.'test'; + $expected = 'test'.PHP_EOL.'make(name(foo), \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testSequentialCompileStringCalls() + { + $string = '@extends(\'foo\') +test'; + $expected = 'test'.PHP_EOL.'make(\'foo\', \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + // use the same compiler instance to compile another template with @extends directive + $string = '@extends(name(foo))'.PHP_EOL.'test'; + $expected = 'test'.PHP_EOL.'make(name(foo), \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeForStatementsTest.php b/tests/View/Blade/BladeForStatementsTest.php new file mode 100644 index 0000000..6ed43fd --- /dev/null +++ b/tests/View/Blade/BladeForStatementsTest.php @@ -0,0 +1,32 @@ + +test +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testNestedForStatementsAreCompiled() + { + $string = '@for ($i = 0; $i < 10; $i++) +@for ($j = 0; $j < 20; $j++) +test +@endfor +@endfor'; + $expected = ' + +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeForeachStatementsTest.php b/tests/View/Blade/BladeForeachStatementsTest.php new file mode 100644 index 0000000..0847b7e --- /dev/null +++ b/tests/View/Blade/BladeForeachStatementsTest.php @@ -0,0 +1,85 @@ +getUsers() as $user) +test +@endforeach'; + $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?> +test +popLoop(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testForeachStatementsAreCompileWithUppercaseSyntax() + { + $string = '@foreach ($this->getUsers() AS $user) +test +@endforeach'; + $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?> +test +popLoop(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testForeachStatementsAreCompileWithMultipleLine() + { + $string = '@foreach ([ +foo, +bar, +] as $label) +test +@endforeach'; + $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $label): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?> +test +popLoop(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testNestedForeachStatementsAreCompiled() + { + $string = '@foreach ($this->getUsers() as $user) +user info +@foreach ($user->tags as $tag) +tag info +@endforeach +@endforeach'; + $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?> +user info +tags; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $tag): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?> +tag info +popLoop(); $loop = $__env->getLastLoop(); ?> +popLoop(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testLoopContentHolderIsExtractedFromForeachStatements() + { + $string = '@foreach ($some_uSers1 as $user)'; + $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = '@foreach ($users->get() as $user)'; + $expected = 'get(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = '@foreach (range(1, 4) as $user)'; + $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = '@foreach ( $users as $user)'; + $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = '@foreach ($tasks as $task)'; + $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $task): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeForelseStatementsTest.php b/tests/View/Blade/BladeForelseStatementsTest.php new file mode 100644 index 0000000..0c04713 --- /dev/null +++ b/tests/View/Blade/BladeForelseStatementsTest.php @@ -0,0 +1,80 @@ +getUsers() as $user) +breeze +@empty +empty +@endforelse'; + $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?> +breeze +popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?> +empty +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testForelseStatementsAreCompiledWithUppercaseSyntax() + { + $string = '@forelse ($this->getUsers() AS $user) +breeze +@empty +empty +@endforelse'; + $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?> +breeze +popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?> +empty +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testForelseStatementsAreCompiledWithMultipleLine() + { + $string = '@forelse ([ +foo, +bar, +] as $label) +breeze +@empty +empty +@endforelse'; + $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $label): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?> +breeze +popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?> +empty +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testNestedForelseStatementsAreCompiled() + { + $string = '@forelse ($this->getUsers() as $user) +@forelse ($user->tags as $tag) +breeze +@empty +tag empty +@endforelse +@empty +empty +@endforelse'; + $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?> +tags; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $tag): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_2 = false; ?> +breeze +popLoop(); $loop = $__env->getLastLoop(); if ($__empty_2): ?> +tag empty + +popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?> +empty +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeHasSectionTest.php b/tests/View/Blade/BladeHasSectionTest.php new file mode 100644 index 0000000..1478891 --- /dev/null +++ b/tests/View/Blade/BladeHasSectionTest.php @@ -0,0 +1,17 @@ +yieldContent("section")))): ?> +breeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeHelpersTest.php b/tests/View/Blade/BladeHelpersTest.php new file mode 100644 index 0000000..3892f84 --- /dev/null +++ b/tests/View/Blade/BladeHelpersTest.php @@ -0,0 +1,15 @@ +assertEquals('', $this->compiler->compileString('@csrf')); + $this->assertEquals('', $this->compiler->compileString("@method('patch')")); + $this->assertEquals('', $this->compiler->compileString('@dd($var1)')); + $this->assertEquals('', $this->compiler->compileString('@dd($var1, $var2)')); + $this->assertEquals('', $this->compiler->compileString('@dump($var1, $var2)')); + } +} diff --git a/tests/View/Blade/BladeIfAuthStatementsTest.php b/tests/View/Blade/BladeIfAuthStatementsTest.php new file mode 100644 index 0000000..a4b2960 --- /dev/null +++ b/tests/View/Blade/BladeIfAuthStatementsTest.php @@ -0,0 +1,45 @@ +getFiles(), __DIR__); + $string = '@auth("api") +breeze +@endauth'; + $expected = 'guard("api")->check()): ?> +breeze +'; + $this->assertEquals($expected, $compiler->compileString($string)); + } + + public function testPlainIfStatementsAreCompiled() + { + $compiler = new BladeCompiler($this->getFiles(), __DIR__); + $string = '@auth +breeze +@endauth'; + $expected = 'guard()->check()): ?> +breeze +'; + $this->assertEquals($expected, $compiler->compileString($string)); + } + + protected function getFiles() + { + return m::mock(Filesystem::class); + } +} diff --git a/tests/View/Blade/BladeIfEmptyStatementsTest.php b/tests/View/Blade/BladeIfEmptyStatementsTest.php new file mode 100644 index 0000000..479f7f2 --- /dev/null +++ b/tests/View/Blade/BladeIfEmptyStatementsTest.php @@ -0,0 +1,17 @@ + +breeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeIfGuestStatementsTest.php b/tests/View/Blade/BladeIfGuestStatementsTest.php new file mode 100644 index 0000000..ba84708 --- /dev/null +++ b/tests/View/Blade/BladeIfGuestStatementsTest.php @@ -0,0 +1,33 @@ +getFiles(), __DIR__); + $string = '@guest("api") +breeze +@endguest'; + $expected = 'guard("api")->guest()): ?> +breeze +'; + $this->assertEquals($expected, $compiler->compileString($string)); + } + + protected function getFiles() + { + return m::mock(Filesystem::class); + } +} diff --git a/tests/View/Blade/BladeIfIssetStatementsTest.php b/tests/View/Blade/BladeIfIssetStatementsTest.php new file mode 100644 index 0000000..a5c749c --- /dev/null +++ b/tests/View/Blade/BladeIfIssetStatementsTest.php @@ -0,0 +1,17 @@ + +breeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeIfStatementsTest.php b/tests/View/Blade/BladeIfStatementsTest.php new file mode 100644 index 0000000..a280bdd --- /dev/null +++ b/tests/View/Blade/BladeIfStatementsTest.php @@ -0,0 +1,56 @@ + +breeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testSwitchstatementsAreCompiled() + { + $string = '@switch(true) +@case(1) +foo + +@case(2) +bar +@endswitch + +foo + +@switch(true) +@case(1) +foo + +@case(2) +bar +@endswitch'; + $expected = ' +foo + + +bar + + +foo + + +foo + + +bar +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeIncludeFirstTest.php b/tests/View/Blade/BladeIncludeFirstTest.php new file mode 100644 index 0000000..097d3f2 --- /dev/null +++ b/tests/View/Blade/BladeIncludeFirstTest.php @@ -0,0 +1,12 @@ +assertEquals('first(["one", "two"], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@includeFirst(["one", "two"])')); + $this->assertEquals('first(["one", "two"], ["foo" => "bar"], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@includeFirst(["one", "two"], ["foo" => "bar"])')); + } +} diff --git a/tests/View/Blade/BladeIncludeIfTest.php b/tests/View/Blade/BladeIncludeIfTest.php new file mode 100644 index 0000000..6818da2 --- /dev/null +++ b/tests/View/Blade/BladeIncludeIfTest.php @@ -0,0 +1,12 @@ +assertEquals('exists(\'foo\')) echo $__env->make(\'foo\', \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@includeIf(\'foo\')')); + $this->assertEquals('exists(name(foo))) echo $__env->make(name(foo), \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@includeIf(name(foo))')); + } +} diff --git a/tests/View/Blade/BladeIncludeTest.php b/tests/View/Blade/BladeIncludeTest.php new file mode 100644 index 0000000..4440c48 --- /dev/null +++ b/tests/View/Blade/BladeIncludeTest.php @@ -0,0 +1,12 @@ +assertEquals('make(\'foo\', \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@include(\'foo\')')); + $this->assertEquals('make(name(foo), \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@include(name(foo))')); + } +} diff --git a/tests/View/Blade/BladeIncludeWhenTest.php b/tests/View/Blade/BladeIncludeWhenTest.php new file mode 100644 index 0000000..ae24d80 --- /dev/null +++ b/tests/View/Blade/BladeIncludeWhenTest.php @@ -0,0 +1,12 @@ +assertEquals('renderWhen(true, \'foo\', ["foo" => "bar"], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\'])); ?>', $this->compiler->compileString('@includeWhen(true, \'foo\', ["foo" => "bar"])')); + $this->assertEquals('renderWhen(true, \'foo\', \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\'])); ?>', $this->compiler->compileString('@includeWhen(true, \'foo\')')); + } +} diff --git a/tests/View/Blade/BladeJsonTest.php b/tests/View/Blade/BladeJsonTest.php new file mode 100644 index 0000000..4995c69 --- /dev/null +++ b/tests/View/Blade/BladeJsonTest.php @@ -0,0 +1,22 @@ +;'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testEncodingOptionsCanBeOverwritten() + { + $string = 'var foo = @json($var, JSON_HEX_TAG);'; + $expected = 'var foo = ;'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeLangTest.php b/tests/View/Blade/BladeLangTest.php new file mode 100644 index 0000000..edc6817 --- /dev/null +++ b/tests/View/Blade/BladeLangTest.php @@ -0,0 +1,19 @@ +getFromJson(function_call('foo(blah)')); ?> bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testLanguageAndChoicesAreCompiled() + { + $this->assertEquals('getFromJson(\'foo\'); ?>', $this->compiler->compileString("@lang('foo')")); + $this->assertEquals('choice(\'foo\', 1); ?>', $this->compiler->compileString("@choice('foo', 1)")); + } +} diff --git a/tests/View/Blade/BladeOverwriteSectionTest.php b/tests/View/Blade/BladeOverwriteSectionTest.php new file mode 100644 index 0000000..e09907e --- /dev/null +++ b/tests/View/Blade/BladeOverwriteSectionTest.php @@ -0,0 +1,11 @@ +assertEquals('stopSection(true); ?>', $this->compiler->compileString('@overwrite')); + } +} diff --git a/tests/View/Blade/BladePhpStatementsTest.php b/tests/View/Blade/BladePhpStatementsTest.php new file mode 100644 index 0000000..1ab2efd --- /dev/null +++ b/tests/View/Blade/BladePhpStatementsTest.php @@ -0,0 +1,46 @@ +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testPhpStatementsWithoutExpressionAreIgnored() + { + $string = '@php'; + $expected = '@php'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + + $string = '{{ "Ignore: @php" }}'; + $expected = ''; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testPhpStatementsDontParseBladeCode() + { + $string = '@php echo "{{ This is a blade tag }}" @endphp'; + $expected = ''; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testVerbatimAndPhpStatementsDontGetMixedUp() + { + $string = "@verbatim {{ Hello, I'm not blade! }}" + ."\n@php echo 'And I'm not PHP!' @endphp" + ."\n@endverbatim {{ 'I am Blade' }}" + ."\n@php echo 'I am PHP {{ not Blade }}' @endphp"; + + $expected = " {{ Hello, I'm not blade! }}" + ."\n@php echo 'And I'm not PHP!' @endphp" + ."\n " + ."\n\n"; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladePrependTest.php b/tests/View/Blade/BladePrependTest.php new file mode 100644 index 0000000..ea2dee4 --- /dev/null +++ b/tests/View/Blade/BladePrependTest.php @@ -0,0 +1,18 @@ +startPrepend(\'foo\'); ?> +bar +stopPrepend(); ?>'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladePushTest.php b/tests/View/Blade/BladePushTest.php new file mode 100644 index 0000000..c43c480 --- /dev/null +++ b/tests/View/Blade/BladePushTest.php @@ -0,0 +1,17 @@ +startPush(\'foo\'); ?> +test +stopPush(); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeSectionTest.php b/tests/View/Blade/BladeSectionTest.php new file mode 100644 index 0000000..1e81ec6 --- /dev/null +++ b/tests/View/Blade/BladeSectionTest.php @@ -0,0 +1,12 @@ +assertEquals('startSection(\'foo\'); ?>', $this->compiler->compileString('@section(\'foo\')')); + $this->assertEquals('startSection(name(foo)); ?>', $this->compiler->compileString('@section(name(foo))')); + } +} diff --git a/tests/View/Blade/BladeShowTest.php b/tests/View/Blade/BladeShowTest.php new file mode 100644 index 0000000..ffdb2a4 --- /dev/null +++ b/tests/View/Blade/BladeShowTest.php @@ -0,0 +1,11 @@ +assertEquals('yieldSection(); ?>', $this->compiler->compileString('@show')); + } +} diff --git a/tests/View/Blade/BladeStackTest.php b/tests/View/Blade/BladeStackTest.php new file mode 100644 index 0000000..0c0fe13 --- /dev/null +++ b/tests/View/Blade/BladeStackTest.php @@ -0,0 +1,13 @@ +yieldPushContent(\'foo\'); ?>'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeStopSectionTest.php b/tests/View/Blade/BladeStopSectionTest.php new file mode 100644 index 0000000..de8eb49 --- /dev/null +++ b/tests/View/Blade/BladeStopSectionTest.php @@ -0,0 +1,11 @@ +assertEquals('stopSection(); ?>', $this->compiler->compileString('@stop')); + } +} diff --git a/tests/View/Blade/BladeUnlessStatementsTest.php b/tests/View/Blade/BladeUnlessStatementsTest.php new file mode 100644 index 0000000..a8634bc --- /dev/null +++ b/tests/View/Blade/BladeUnlessStatementsTest.php @@ -0,0 +1,17 @@ + +breeze +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeUnsetStatementsTest.php b/tests/View/Blade/BladeUnsetStatementsTest.php new file mode 100644 index 0000000..32765f6 --- /dev/null +++ b/tests/View/Blade/BladeUnsetStatementsTest.php @@ -0,0 +1,13 @@ +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeVerbatimTest.php b/tests/View/Blade/BladeVerbatimTest.php new file mode 100644 index 0000000..2b9f836 --- /dev/null +++ b/tests/View/Blade/BladeVerbatimTest.php @@ -0,0 +1,89 @@ +assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testVerbatimBlocksWithMultipleLinesAreCompiled() + { + $string = 'Some text +@verbatim + {{ $a }} + @if($b) + {{ $b }} + @endif +@endverbatim'; + $expected = 'Some text + + {{ $a }} + @if($b) + {{ $b }} + @endif +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testMultipleVerbatimBlocksAreCompiled() + { + $string = '@verbatim {{ $a }} @endverbatim {{ $b }} @verbatim {{ $c }} @endverbatim'; + $expected = ' {{ $a }} {{ $c }} '; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testRawBlocksAreRenderedInTheRightOrder() + { + $string = '@php echo "#1"; @endphp @verbatim {{ #2 }} @endverbatim @verbatim {{ #3 }} @endverbatim @php echo "#4"; @endphp'; + + $expected = ' {{ #2 }} {{ #3 }} '; + + $this->assertSame($expected, $this->compiler->compileString($string)); + } + + public function testMultilineTemplatesWithRawBlocksAreRenderedInTheRightOrder() + { + $string = '{{ $first }} +@php + echo $second; +@endphp +@if ($conditional) + {{ $third }} +@endif +@include("users") +@verbatim + {{ $fourth }} @include("test") +@endverbatim +@php echo $fifth; @endphp'; + + $expected = ' + + + + + + +make("users", \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?> + + {{ $fourth }} @include("test") + +'; + + $this->assertSame($expected, $this->compiler->compileString($string)); + } + + public function testRawBlocksDontGetMixedUpWhenSomeAreRemovedByBladeComments() + { + $string = '{{-- @verbatim Block #1 @endverbatim --}} @php "Block #2" @endphp'; + $expected = ' '; + + $this->assertSame($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeWhileStatementsTest.php b/tests/View/Blade/BladeWhileStatementsTest.php new file mode 100644 index 0000000..c87af9b --- /dev/null +++ b/tests/View/Blade/BladeWhileStatementsTest.php @@ -0,0 +1,32 @@ + +test +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testNestedWhileStatementsAreCompiled() + { + $string = '@while ($foo) +@while ($bar) +test +@endwhile +@endwhile'; + $expected = ' + +test + +'; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/Blade/BladeYieldTest.php b/tests/View/Blade/BladeYieldTest.php new file mode 100644 index 0000000..c518078 --- /dev/null +++ b/tests/View/Blade/BladeYieldTest.php @@ -0,0 +1,13 @@ +assertEquals('yieldContent(\'foo\'); ?>', $this->compiler->compileString('@yield(\'foo\')')); + $this->assertEquals('yieldContent(\'foo\', \'bar\'); ?>', $this->compiler->compileString('@yield(\'foo\', \'bar\')')); + $this->assertEquals('yieldContent(name(foo)); ?>', $this->compiler->compileString('@yield(name(foo))')); + } +} diff --git a/tests/View/DataObjectStub.php b/tests/View/DataObjectStub.php new file mode 100644 index 0000000..5b59758 --- /dev/null +++ b/tests/View/DataObjectStub.php @@ -0,0 +1,7 @@ + 0], 'path', []); + $view = $this->getView(); $view->with('foo', 'bar'); $view->with(['baz' => 'boom']); $this->assertEquals(['foo' => 'bar', 'baz' => 'boom'], $view->getData()); - $view = new StringView(m::mock('Wpb\String_Blade_Compiler\Factory'), m::mock('Illuminate\Contracts\View\Engine'), ['secondsTemplateCacheExpires' => 0], 'path', []); + $view = $this->getView(); $view->withFoo('bar')->withBaz('boom'); $this->assertEquals(['foo' => 'bar', 'baz' => 'boom'], $view->getData()); } public function testRenderProperlyRendersView() { - // unable to get to work ... still trying to figure out why - /* - $view = $this->getView(); + $view = $this->getView(['foo' => 'bar']); $view->getFactory()->shouldReceive('incrementRender')->once()->ordered(); $view->getFactory()->shouldReceive('callComposer')->once()->ordered()->with($view); $view->getFactory()->shouldReceive('getShared')->once()->andReturn(['shared' => 'foo']); $view->getEngine()->shouldReceive('get')->once()->with('path', ['foo' => 'bar', 'shared' => 'foo'])->andReturn('contents'); $view->getFactory()->shouldReceive('decrementRender')->once()->ordered(); - $view->getFactory()->shouldReceive('flushSectionsIfDoneRendering')->once(); + $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->once(); - $me = $this; - $callback = function (StringView $rendered, $contents) use ($me, $view) { - $me->assertEquals($view, $rendered); - $me->assertEquals('contents', $contents); + $callback = function (View $rendered, $contents) use ($view) { + $this->assertEquals($view, $rendered); + $this->assertEquals('contents', $contents); }; $this->assertEquals('contents', $view->render($callback)); - */ + } + + public function testRenderHandlingCallbackReturnValues() + { + $view = $this->getView(); + $view->getFactory()->shouldReceive('incrementRender'); + $view->getFactory()->shouldReceive('callComposer'); + $view->getFactory()->shouldReceive('getShared')->andReturn(['shared' => 'foo']); + $view->getEngine()->shouldReceive('get')->andReturn('contents'); + $view->getFactory()->shouldReceive('decrementRender'); + $view->getFactory()->shouldReceive('flushStateIfDoneRendering'); + + $this->assertEquals('new contents', $view->render(function () { + return 'new contents'; + })); + + $this->assertEmpty($view->render(function () { + return ''; + })); + + $this->assertEquals('contents', $view->render(function () { + // + })); } public function testRenderSectionsReturnsEnvironmentSections() { - $view = m::mock('Wpb\String_Blade_Compiler\View[render]', [ - m::mock('Wpb\String_Blade_Compiler\Factory'), - m::mock('Illuminate\Contracts\View\Engine'), - ['secondsTemplateCacheExpires' => 0], + $view = m::mock(View::class.'[render]', [ + m::mock(Factory::class), + m::mock(Engine::class), + [ + // this actual blade template + 'template' => '{{ $token1 }}', + // this is the cache file key, converted to md5 + 'cache_key' => 'my_unique_cache_key', + // number of seconds needed in order to recompile, 0 is always recompile + 'secondsTemplateCacheExpires' => 1391973007 + ], 'path', [], ]); - $view->shouldReceive('render')->with(m::type('Closure'))->once()->andReturn($sections = ['foo' => 'bar']); + $view->shouldReceive('render')->with(m::type(Closure::class))->once()->andReturn($sections = ['foo' => 'bar']); $this->assertEquals($sections, $view->renderSections()); } public function testSectionsAreNotFlushedWhenNotDoneRendering() { - // unable to get to work ... still trying to figure out why - /* - $view = $this->getView(); + $view = $this->getView(['foo' => 'bar']); $view->getFactory()->shouldReceive('incrementRender')->twice(); $view->getFactory()->shouldReceive('callComposer')->twice()->with($view); $view->getFactory()->shouldReceive('getShared')->twice()->andReturn(['shared' => 'foo']); $view->getEngine()->shouldReceive('get')->twice()->with('path', ['foo' => 'bar', 'shared' => 'foo'])->andReturn('contents'); $view->getFactory()->shouldReceive('decrementRender')->twice(); - $view->getFactory()->shouldReceive('flushSectionsIfDoneRendering')->twice(); + $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->twice(); $this->assertEquals('contents', $view->render()); $this->assertEquals('contents', (string) $view); - */ } public function testViewNestBindsASubView() @@ -82,21 +118,15 @@ public function testViewNestBindsASubView() $view->getFactory()->shouldReceive('make')->once()->with('foo', ['data']); $result = $view->nest('key', 'foo', ['data']); - $this->assertInstanceOf('Wpb\String_Blade_Compiler\View', $result); + $this->assertInstanceOf(View::class, $result); } public function testViewAcceptsArrayableImplementations() { - $arrayable = m::mock('Illuminate\Contracts\Support\Arrayable'); + $arrayable = m::mock(Arrayable::class); $arrayable->shouldReceive('toArray')->once()->andReturn(['foo' => 'bar', 'baz' => ['qux', 'corge']]); - $view = new StringView( - m::mock('Wpb\String_Blade_Compiler\Factory'), - m::mock('Illuminate\Contracts\View\Engine'), - ['secondsTemplateCacheExpires' => 0], - 'path', - $arrayable->toArray() // StringView is not detecting the mock as a arrayable, no clue why - ); + $view = $this->getView($arrayable); $this->assertEquals('bar', $view->foo); $this->assertEquals(['qux', 'corge'], $view->baz); @@ -104,10 +134,9 @@ public function testViewAcceptsArrayableImplementations() public function testViewGettersSetters() { - $view = $this->getView(); - $this->assertEquals($view->getName(), md5('')); - // path is an array, containing secondsTemplateCacheExpire, template, cache_key - //$this->assertEquals($view->getPath(), 'path'); + $view = $this->getView(['foo' => 'bar']); + $this->assertEquals($view->name(), md5($view->getViewTemplate())); + $this->assertEquals($view->getPath(), 'path'); $data = $view->getData(); $this->assertEquals($data['foo'], 'bar'); $view->setPath('newPath'); @@ -116,8 +145,20 @@ public function testViewGettersSetters() public function testViewArrayAccess() { - $view = $this->getView(); - $this->assertInstanceOf('ArrayAccess', $view); + $view = $this->getView(['foo' => 'bar']); + $this->assertInstanceOf(ArrayAccess::class, $view); + $this->assertTrue($view->offsetExists('foo')); + $this->assertEquals($view->offsetGet('foo'), 'bar'); + $view->offsetSet('foo', 'baz'); + $this->assertEquals($view->offsetGet('foo'), 'baz'); + $view->offsetUnset('foo'); + $this->assertFalse($view->offsetExists('foo')); + } + + public function testViewConstructedWithObjectData() + { + $view = $this->getView(new DataObjectStub); + $this->assertInstanceOf(ArrayAccess::class, $view); $this->assertTrue($view->offsetExists('foo')); $this->assertEquals($view->offsetGet('foo'), 'bar'); $view->offsetSet('foo', 'baz'); @@ -128,7 +169,7 @@ public function testViewArrayAccess() public function testViewMagicMethods() { - $view = $this->getView(); + $view = $this->getView(['foo' => 'bar']); $this->assertTrue(isset($view->foo)); $this->assertEquals($view->foo, 'bar'); $view->foo = 'baz'; @@ -141,7 +182,9 @@ public function testViewMagicMethods() public function testViewBadMethod() { - $this->setExpectedException('BadMethodCallException'); + $this->expectException(BadMethodCallException::class); + $this->expectExceptionMessage('Method Wpb\String_Blade_Compiler\StringView::badMethodCall does not exist.'); + $view = $this->getView(); $view->badMethodCall(); } @@ -154,9 +197,9 @@ public function testViewGatherDataWithRenderable() $view->getFactory()->shouldReceive('getShared')->once()->andReturn(['shared' => 'foo']); $view->getEngine()->shouldReceive('get')->once()->andReturn('contents'); $view->getFactory()->shouldReceive('decrementRender')->once()->ordered(); - $view->getFactory()->shouldReceive('flushSectionsIfDoneRendering')->once(); + $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->once(); - $view->renderable = m::mock('Illuminate\Contracts\Support\Renderable'); + $view->renderable = m::mock(Renderable::class); $view->renderable->shouldReceive('render')->once()->andReturn('text'); $this->assertEquals('contents', $view->render()); } @@ -169,7 +212,7 @@ public function testViewRenderSections() $view->getFactory()->shouldReceive('getShared')->once()->andReturn(['shared' => 'foo']); $view->getEngine()->shouldReceive('get')->once()->andReturn('contents'); $view->getFactory()->shouldReceive('decrementRender')->once()->ordered(); - $view->getFactory()->shouldReceive('flushSectionsIfDoneRendering')->once(); + $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->once(); $view->getFactory()->shouldReceive('getSections')->once()->andReturn(['foo', 'bar']); $sections = $view->renderSections(); @@ -182,25 +225,32 @@ public function testWithErrors() $view = $this->getView(); $errors = ['foo' => 'bar', 'qu' => 'ux']; $this->assertSame($view, $view->withErrors($errors)); - $this->assertInstanceOf('Illuminate\Support\MessageBag', $view->errors); + $this->assertInstanceOf(MessageBag::class, $view->errors); $foo = $view->errors->get('foo'); $this->assertEquals($foo[0], 'bar'); $qu = $view->errors->get('qu'); $this->assertEquals($qu[0], 'ux'); $data = ['foo' => 'baz']; - $this->assertSame($view, $view->withErrors(new \Illuminate\Support\MessageBag($data))); + $this->assertSame($view, $view->withErrors(new MessageBag($data))); $foo = $view->errors->get('foo'); $this->assertEquals($foo[0], 'baz'); } - protected function getView() + protected function getView($data = []) { - return new StringView( - m::mock('Wpb\String_Blade_Compiler\Factory'), - m::mock('Illuminate\Contracts\View\Engine'), - ['secondsTemplateCacheExpires' => 0], + return new View( + m::mock(Factory::class), + m::mock(Engine::class), + [ + // this actual blade template + 'template' => '{{ $token1 }}', + // this is the cache file key, converted to md5 + 'cache_key' => 'my_unique_cache_key', + // number of seconds needed in order to recompile, 0 is always recompile + 'secondsTemplateCacheExpires' => 1391973007 + ], 'path', - ['foo' => 'bar'] + $data ); } -} \ No newline at end of file +} diff --git a/tests/View/ViewCompilerEngineTest.php b/tests/View/ViewCompilerEngineTest.php new file mode 100644 index 0000000..eefca1e --- /dev/null +++ b/tests/View/ViewCompilerEngineTest.php @@ -0,0 +1,46 @@ +getEngine(); + $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__.'/fixtures/foo.php')->andReturn(__DIR__.'/fixtures/basic.php'); + $engine->getCompiler()->shouldReceive('isExpired')->once()->with(__DIR__.'/fixtures/foo.php')->andReturn(true); + $engine->getCompiler()->shouldReceive('compile')->once()->with(__DIR__.'/fixtures/foo.php'); + $results = $engine->get(__DIR__.'/fixtures/foo.php'); + + $this->assertEquals('Hello World +', $results); + } + + public function testViewsAreNotRecompiledIfTheyAreNotExpired() + { + $engine = $this->getEngine(); + $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__.'/fixtures/foo.php')->andReturn(__DIR__.'/fixtures/basic.php'); + $engine->getCompiler()->shouldReceive('isExpired')->once()->andReturn(false); + $engine->getCompiler()->shouldReceive('compile')->never(); + $results = $engine->get(__DIR__.'/fixtures/foo.php'); + + $this->assertEquals('Hello World +', $results); + } + + protected function getEngine() + { + return new CompilerEngine(m::mock(CompilerInterface::class)); + } +} diff --git a/tests/string_blade_compiler/ViewFactoryTest.php b/tests/View/ViewFactoryTest.php similarity index 50% rename from tests/string_blade_compiler/ViewFactoryTest.php rename to tests/View/ViewFactoryTest.php index bc78b64..b592bb7 100644 --- a/tests/string_blade_compiler/ViewFactoryTest.php +++ b/tests/View/ViewFactoryTest.php @@ -1,11 +1,31 @@ getFactory(); $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn('path.php'); - $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('php')->andReturn($engine = m::mock('Illuminate\Contracts\View\Engine')); + $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('php')->andReturn($engine = m::mock(Engine::class)); $factory->getFinder()->shouldReceive('addExtension')->once()->with('php'); - $factory->setDispatcher(new Illuminate\Events\Dispatcher); - $factory->creator('view', function ($view) { $_SERVER['__test.view'] = $view; }); + $factory->setDispatcher(new Dispatcher); + $factory->creator('view', function ($view) { + $_SERVER['__test.view'] = $view; + }); $factory->addExtension('php', 'php'); $view = $factory->make('view', ['foo' => 'bar'], ['baz' => 'boom']); @@ -32,7 +54,7 @@ public function testMakeCreatesNewViewInstanceWithProperPathAndEngine() public function testExistsPassesAndFailsViews() { $factory = $this->getFactory(); - $factory->getFinder()->shouldReceive('find')->once()->with('foo')->andThrow('InvalidArgumentException'); + $factory->getFinder()->shouldReceive('find')->once()->with('foo')->andThrow(InvalidArgumentException::class); $factory->getFinder()->shouldReceive('find')->once()->with('bar')->andReturn('path.php'); $this->assertFalse($factory->exists('foo')); @@ -45,10 +67,10 @@ public function testFirstCreatesNewViewInstanceWithProperPath() $factory = $this->getFactory(); $factory->getFinder()->shouldReceive('find')->twice()->with('view')->andReturn('path.php'); - $factory->getFinder()->shouldReceive('find')->once()->with('bar')->andThrow('InvalidArgumentException'); - $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('php')->andReturn($engine = m::mock(\Illuminate\Contracts\View\Engine::class)); + $factory->getFinder()->shouldReceive('find')->once()->with('bar')->andThrow(InvalidArgumentException::class); + $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('php')->andReturn($engine = m::mock(Engine::class)); $factory->getFinder()->shouldReceive('addExtension')->once()->with('php'); - $factory->setDispatcher(new \Illuminate\Events\Dispatcher); + $factory->setDispatcher(new Dispatcher); $factory->creator('view', function ($view) { $_SERVER['__test.view'] = $view; }); @@ -61,25 +83,24 @@ public function testFirstCreatesNewViewInstanceWithProperPath() unset($_SERVER['__test.view']); } - /** - * @expectedException InvalidArgumentException - */ public function testFirstThrowsInvalidArgumentExceptionIfNoneFound() { + $this->expectException(InvalidArgumentException::class); + $factory = $this->getFactory(); - $factory->getFinder()->shouldReceive('find')->once()->with('view')->andThrow('InvalidArgumentException'); - $factory->getFinder()->shouldReceive('find')->once()->with('bar')->andThrow('InvalidArgumentException'); - $factory->getEngineResolver()->shouldReceive('resolve')->with('php')->andReturn($engine = m::mock(\Illuminate\Contracts\View\Engine::class)); + $factory->getFinder()->shouldReceive('find')->once()->with('view')->andThrow(InvalidArgumentException::class); + $factory->getFinder()->shouldReceive('find')->once()->with('bar')->andThrow(InvalidArgumentException::class); + $factory->getEngineResolver()->shouldReceive('resolve')->with('php')->andReturn($engine = m::mock(Engine::class)); $factory->getFinder()->shouldReceive('addExtension')->with('php'); $factory->addExtension('php', 'php'); - $view = $factory->first(['bar', 'view'], ['foo' => 'bar'], ['baz' => 'boom']); + $factory->first(['bar', 'view'], ['foo' => 'bar'], ['baz' => 'boom']); } public function testRenderEachCreatesViewForEachItemInArray() { - $factory = m::mock('Wpb\String_Blade_Compiler\Factory[make]', $this->getFactoryArgs()); - $factory->shouldReceive('make')->once()->with('foo', ['key' => 'bar', 'value' => 'baz'])->andReturn($mockView1 = m::mock('StdClass')); - $factory->shouldReceive('make')->once()->with('foo', ['key' => 'breeze', 'value' => 'boom'])->andReturn($mockView2 = m::mock('StdClass')); + $factory = m::mock(Factory::class.'[make]', $this->getFactoryArgs()); + $factory->shouldReceive('make')->once()->with('foo', ['key' => 'bar', 'value' => 'baz'])->andReturn($mockView1 = m::mock(stdClass::class)); + $factory->shouldReceive('make')->once()->with('foo', ['key' => 'breeze', 'value' => 'boom'])->andReturn($mockView2 = m::mock(stdClass::class)); $mockView1->shouldReceive('render')->once()->andReturn('dayle'); $mockView2->shouldReceive('render')->once()->andReturn('rees'); @@ -90,35 +111,13 @@ public function testRenderEachCreatesViewForEachItemInArray() public function testEmptyViewsCanBeReturnedFromRenderEach() { - $factory = m::mock('Wpb\String_Blade_Compiler\Factory[make]', $this->getFactoryArgs()); - $factory->shouldReceive('make')->once()->with('foo')->andReturn($mockView = m::mock('StdClass')); + $factory = m::mock(Factory::class.'[make]', $this->getFactoryArgs()); + $factory->shouldReceive('make')->once()->with('foo')->andReturn($mockView = m::mock(stdClass::class)); $mockView->shouldReceive('render')->once()->andReturn('empty'); $this->assertEquals('empty', $factory->renderEach('view', [], 'iterator', 'foo')); } - public function testAddANamedViews() - { - $factory = $this->getFactory(); - $factory->name('bar', 'foo'); - - $this->assertEquals(['foo' => 'bar'], $factory->getNames()); - } - - public function testMakeAViewFromNamedView() - { - $factory = $this->getFactory(); - $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn('path.php'); - $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('php')->andReturn($engine = m::mock('Illuminate\Contracts\View\Engine')); - $factory->getFinder()->shouldReceive('addExtension')->once()->with('php'); - $factory->getDispatcher()->shouldReceive('fire'); - $factory->addExtension('php', 'php'); - $factory->name('view', 'foo'); - $view = $factory->of('foo', ['data']); - - $this->assertSame($engine, $view->getEngine()); - } - public function testRawStringsMayBeReturnedFromRenderEach() { $this->assertEquals('foo', $this->getFactory()->renderEach('foo', [], 'item', 'raw|foo')); @@ -128,13 +127,15 @@ public function testEnvironmentAddsExtensionWithCustomResolver() { $factory = $this->getFactory(); - $resolver = function () {}; + $resolver = function () { + // + }; $factory->getFinder()->shouldReceive('addExtension')->once()->with('foo'); $factory->getEngineResolver()->shouldReceive('register')->once()->with('bar', $resolver); $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn('path.foo'); - $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('bar')->andReturn($engine = m::mock('Illuminate\Contracts\View\Engine')); - $factory->getDispatcher()->shouldReceive('fire'); + $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('bar')->andReturn($engine = m::mock(Engine::class)); + $factory->getDispatcher()->shouldReceive('dispatch'); $factory->addExtension('foo', 'bar', $resolver); @@ -171,18 +172,10 @@ public function testPrependedExtensionOverridesExistingExtensions() public function testComposersAreProperlyRegistered() { $factory = $this->getFactory(); - $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type('Closure')); - $callback = $factory->composer('foo', function () { return 'bar'; }); - $callback = $callback[0]; - - $this->assertEquals('bar', $callback()); - } - - public function testComposersAreProperlyRegisteredWithPriority() - { - $factory = $this->getFactory(); - $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type('Closure'), 1); - $callback = $factory->composer('foo', function () { return 'bar'; }, 1); + $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type(Closure::class)); + $callback = $factory->composer('foo', function () { + return 'bar'; + }); $callback = $callback[0]; $this->assertEquals('bar', $callback()); @@ -191,9 +184,9 @@ public function testComposersAreProperlyRegisteredWithPriority() public function testComposersCanBeMassRegistered() { $factory = $this->getFactory(); - $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: bar', m::type('Closure')); - $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: qux', m::type('Closure')); - $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type('Closure')); + $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: bar', m::type(Closure::class)); + $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: qux', m::type(Closure::class)); + $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type(Closure::class)); $composers = $factory->composers([ 'foo' => 'bar', 'baz@baz' => ['qux', 'foo'], @@ -211,9 +204,9 @@ public function testComposersCanBeMassRegistered() public function testClassCallbacks() { $factory = $this->getFactory(); - $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type('Closure')); - $factory->setContainer($container = m::mock('Illuminate\Container\Container')); - $container->shouldReceive('make')->once()->with('FooComposer')->andReturn($composer = m::mock('StdClass')); + $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type(Closure::class)); + $factory->setContainer($container = m::mock(Container::class)); + $container->shouldReceive('make')->once()->with('FooComposer')->andReturn($composer = m::mock(stdClass::class)); $composer->shouldReceive('compose')->once()->with('view')->andReturn('composed'); $callback = $factory->composer('foo', 'FooComposer'); $callback = $callback[0]; @@ -224,9 +217,9 @@ public function testClassCallbacks() public function testClassCallbacksWithMethods() { $factory = $this->getFactory(); - $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type('Closure')); - $factory->setContainer($container = m::mock('Illuminate\Container\Container')); - $container->shouldReceive('make')->once()->with('FooComposer')->andReturn($composer = m::mock('StdClass')); + $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type(Closure::class)); + $factory->setContainer($container = m::mock(Container::class)); + $container->shouldReceive('make')->once()->with('FooComposer')->andReturn($composer = m::mock(stdClass::class)); $composer->shouldReceive('doComposer')->once()->with('view')->andReturn('composed'); $callback = $factory->composer('foo', 'FooComposer@doComposer'); $callback = $callback[0]; @@ -237,9 +230,9 @@ public function testClassCallbacksWithMethods() public function testCallComposerCallsProperEvent() { $factory = $this->getFactory(); - $view = m::mock('Wpb\String_Blade_Compiler\View'); - $view->shouldReceive('getName')->once()->andReturn('name'); - $factory->getDispatcher()->shouldReceive('fire')->once()->with('composing: name', [$view]); + $view = m::mock(View::class); + $view->shouldReceive('name')->once()->andReturn('name'); + $factory->getDispatcher()->shouldReceive('dispatch')->once()->with('composing: name', [$view]); $factory->callComposer($view); } @@ -261,6 +254,26 @@ public function testRenderCountHandling() $this->assertTrue($factory->doneRendering()); } + public function testYieldDefault() + { + $factory = $this->getFactory(); + $this->assertEquals('hi', $factory->yieldContent('foo', 'hi')); + } + + public function testYieldDefaultIsEscaped() + { + $factory = $this->getFactory(); + $this->assertEquals('<p>hi</p>', $factory->yieldContent('foo', '

hi

')); + } + + public function testYieldDefaultViewIsNotEscapedTwice() + { + $factory = $this->getFactory(); + $view = m::mock(View::class); + $view->shouldReceive('__toString')->once()->andReturn('

hi

<p>already escaped</p>'); + $this->assertEquals('

hi

<p>already escaped</p>', $factory->yieldContent('foo', $view)); + } + public function testBasicSectionHandling() { $factory = $this->getFactory(); @@ -270,11 +283,35 @@ public function testBasicSectionHandling() $this->assertEquals('hi', $factory->yieldContent('foo')); } + public function testBasicSectionDefault() + { + $factory = $this->getFactory(); + $factory->startSection('foo', 'hi'); + $this->assertEquals('hi', $factory->yieldContent('foo')); + } + + public function testBasicSectionDefaultIsEscaped() + { + $factory = $this->getFactory(); + $factory->startSection('foo', '

hi

'); + $this->assertEquals('<p>hi</p>', $factory->yieldContent('foo')); + } + + public function testBasicSectionDefaultViewIsNotEscapedTwice() + { + $factory = $this->getFactory(); + $view = m::mock(View::class); + $view->shouldReceive('__toString')->once()->andReturn('

hi

<p>already escaped</p>'); + $factory->startSection('foo', $view); + $this->assertEquals('

hi

<p>already escaped</p>', $factory->yieldContent('foo')); + } + public function testSectionExtending() { + $placeholder = Factory::parentPlaceholder('foo'); $factory = $this->getFactory(); $factory->startSection('foo'); - echo 'hi @parent'; + echo 'hi '.$placeholder; $factory->stopSection(); $factory->startSection('foo'); echo 'there'; @@ -282,25 +319,71 @@ public function testSectionExtending() $this->assertEquals('hi there', $factory->yieldContent('foo')); } - public function testSingleStackPush() + public function testSectionMultipleExtending() { + $placeholder = Factory::parentPlaceholder('foo'); $factory = $this->getFactory(); $factory->startSection('foo'); + echo 'hello '.$placeholder.' nice to see you '.$placeholder; + $factory->stopSection(); + $factory->startSection('foo'); + echo 'my '.$placeholder; + $factory->stopSection(); + $factory->startSection('foo'); + echo 'friend'; + $factory->stopSection(); + $this->assertEquals('hello my friend nice to see you my friend', $factory->yieldContent('foo')); + } + + public function testComponentHandling() + { + $factory = $this->getFactory(); + $factory->getFinder()->shouldReceive('find')->andReturn(__DIR__.'/fixtures/component.php'); + $factory->getEngineResolver()->shouldReceive('resolve')->andReturn(new PhpEngine); + $factory->getDispatcher()->shouldReceive('dispatch'); + $factory->startComponent('component', ['name' => 'Taylor']); + $factory->slot('title'); + $factory->slot('website', 'laravel.com'); + echo 'title
'; + $factory->endSlot(); + echo 'component'; + $contents = $factory->renderComponent(); + $this->assertEquals('title
component Taylor laravel.com', $contents); + } + + public function testTranslation() + { + $container = new Container; + $container->instance('translator', $translator = m::mock(stdClass::class)); + $translator->shouldReceive('getFromJson')->with('Foo', ['name' => 'taylor'])->andReturn('Bar'); + $factory = $this->getFactory(); + $factory->setContainer($container); + $factory->startTranslation(['name' => 'taylor']); + echo 'Foo'; + $string = $factory->renderTranslation(); + + $this->assertEquals('Bar', $string); + } + + public function testSingleStackPush() + { + $factory = $this->getFactory(); + $factory->startPush('foo'); echo 'hi'; - $factory->appendSection(); - $this->assertEquals('hi', $factory->yieldContent('foo')); + $factory->stopPush(); + $this->assertEquals('hi', $factory->yieldPushContent('foo')); } public function testMultipleStackPush() { $factory = $this->getFactory(); - $factory->startSection('foo'); + $factory->startPush('foo'); echo 'hi'; - $factory->appendSection(); - $factory->startSection('foo'); + $factory->stopPush(); + $factory->startPush('foo'); echo ', Hello!'; - $factory->appendSection(); - $this->assertEquals('hi, Hello!', $factory->yieldContent('foo')); + $factory->stopPush(); + $this->assertEquals('hi, Hello!', $factory->yieldPushContent('foo')); } public function testSessionAppending() @@ -333,7 +416,7 @@ public function testInjectStartsSectionWithContent() public function testEmptyStringIsReturnedForNonSections() { $factory = $this->getFactory(); - $this->assertEquals('', $factory->yieldContent('foo')); + $this->assertEmpty($factory->yieldContent('foo')); } public function testSectionFlushing() @@ -361,12 +444,24 @@ public function testHasSection() $this->assertFalse($factory->hasSection('bar')); } + public function testGetSection() + { + $factory = $this->getFactory(); + $factory->startSection('foo'); + echo 'hi'; + $factory->stopSection(); + + $this->assertEquals('hi', $factory->getSection('foo')); + $this->assertNull($factory->getSection('bar')); + $this->assertEquals('default', $factory->getSection('bar', 'default')); + } + public function testMakeWithSlashAndDot() { $factory = $this->getFactory(); $factory->getFinder()->shouldReceive('find')->twice()->with('foo.bar')->andReturn('path.php'); - $factory->getEngineResolver()->shouldReceive('resolve')->twice()->with('php')->andReturn(m::mock('Illuminate\Contracts\View\Engine')); - $factory->getDispatcher()->shouldReceive('fire'); + $factory->getEngineResolver()->shouldReceive('resolve')->twice()->with('php')->andReturn(m::mock(Engine::class)); + $factory->getDispatcher()->shouldReceive('dispatch'); $factory->make('foo/bar'); $factory->make('foo.bar'); } @@ -375,28 +470,16 @@ public function testNamespacedViewNamesAreNormalizedProperly() { $factory = $this->getFactory(); $factory->getFinder()->shouldReceive('find')->twice()->with('vendor/package::foo.bar')->andReturn('path.php'); - $factory->getEngineResolver()->shouldReceive('resolve')->twice()->with('php')->andReturn(m::mock('Illuminate\Contracts\View\Engine')); - $factory->getDispatcher()->shouldReceive('fire'); + $factory->getEngineResolver()->shouldReceive('resolve')->twice()->with('php')->andReturn(m::mock(Engine::class)); + $factory->getDispatcher()->shouldReceive('dispatch'); $factory->make('vendor/package::foo/bar'); $factory->make('vendor/package::foo.bar'); } - public function testMakeWithAlias() - { - $factory = $this->getFactory(); - $factory->alias('real', 'alias'); - $factory->getFinder()->shouldReceive('find')->once()->with('real')->andReturn('path.php'); - $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('php')->andReturn(m::mock('Illuminate\Contracts\View\Engine')); - $factory->getDispatcher()->shouldReceive('fire'); - - $view = $factory->make('alias'); - - $this->assertEquals('real', $view->getName()); - } - public function testExceptionIsThrownForUnknownExtension() { - $this->setExpectedException('InvalidArgumentException'); + $this->expectException(InvalidArgumentException::class); + $factory = $this->getFactory(); $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn('view.foo'); $factory->make('view'); @@ -404,34 +487,208 @@ public function testExceptionIsThrownForUnknownExtension() public function testExceptionsInSectionsAreThrown() { - $engine = new \Illuminate\View\Engines\CompilerEngine(m::mock('Illuminate\View\Compilers\CompilerInterface')); - $engine->getCompiler()->shouldReceive('getCompiledPath')->andReturnUsing(function ($path) { return $path; }); + $this->expectException(ErrorException::class); + $this->expectExceptionMessage('section exception message'); + + $engine = new CompilerEngine(m::mock(CompilerInterface::class)); + $engine->getCompiler()->shouldReceive('getCompiledPath')->andReturnUsing(function ($path) { + return $path; + }); $engine->getCompiler()->shouldReceive('isExpired')->twice()->andReturn(false); $factory = $this->getFactory(); $factory->getEngineResolver()->shouldReceive('resolve')->twice()->andReturn($engine); $factory->getFinder()->shouldReceive('find')->once()->with('layout')->andReturn(__DIR__.'/fixtures/section-exception-layout.php'); $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn(__DIR__.'/fixtures/section-exception.php'); - $factory->getDispatcher()->shouldReceive('fire')->times(4); + $factory->getDispatcher()->shouldReceive('dispatch')->times(4); - $this->setExpectedException('Exception', 'section exception message'); $factory->make('view')->render(); } + public function testExtraStopSectionCallThrowsException() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Cannot end a section without first starting one.'); + + $factory = $this->getFactory(); + $factory->startSection('foo'); + $factory->stopSection(); + + $factory->stopSection(); + } + + public function testExtraAppendSectionCallThrowsException() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Cannot end a section without first starting one.'); + + $factory = $this->getFactory(); + $factory->startSection('foo'); + $factory->stopSection(); + + $factory->appendSection(); + } + + public function testAddingLoops() + { + $factory = $this->getFactory(); + + $factory->addLoop([1, 2, 3]); + + $expectedLoop = [ + 'iteration' => 0, + 'index' => 0, + 'remaining' => 3, + 'count' => 3, + 'first' => true, + 'last' => false, + 'odd' => false, + 'even' => true, + 'depth' => 1, + 'parent' => null, + ]; + + $this->assertEquals([$expectedLoop], $factory->getLoopStack()); + + $factory->addLoop([1, 2, 3, 4]); + + $secondExpectedLoop = [ + 'iteration' => 0, + 'index' => 0, + 'remaining' => 4, + 'count' => 4, + 'first' => true, + 'last' => false, + 'odd' => false, + 'even' => true, + 'depth' => 2, + 'parent' => (object) $expectedLoop, + ]; + $this->assertEquals([$expectedLoop, $secondExpectedLoop], $factory->getLoopStack()); + + $factory->popLoop(); + + $this->assertEquals([$expectedLoop], $factory->getLoopStack()); + } + + public function testAddingLoopDoesNotCloseGenerator() + { + $factory = $this->getFactory(); + + $data = (new class { + public function generate() + { + for ($count = 0; $count < 3; $count++) { + yield ['a', 'b']; + } + } + })->generate(); + + $factory->addLoop($data); + + foreach ($data as $chunk) { + $this->assertEquals(['a', 'b'], $chunk); + } + } + + public function testAddingUncountableLoop() + { + $factory = $this->getFactory(); + + $factory->addLoop(''); + + $expectedLoop = [ + 'iteration' => 0, + 'index' => 0, + 'remaining' => null, + 'count' => null, + 'first' => true, + 'last' => null, + 'odd' => false, + 'even' => true, + 'depth' => 1, + 'parent' => null, + ]; + + $this->assertEquals([$expectedLoop], $factory->getLoopStack()); + } + + public function testIncrementingLoopIndices() + { + $factory = $this->getFactory(); + + $factory->addLoop([1, 2, 3, 4]); + + $factory->incrementLoopIndices(); + + $this->assertEquals(1, $factory->getLoopStack()[0]['iteration']); + $this->assertEquals(0, $factory->getLoopStack()[0]['index']); + $this->assertEquals(3, $factory->getLoopStack()[0]['remaining']); + $this->assertTrue($factory->getLoopStack()[0]['odd']); + $this->assertFalse($factory->getLoopStack()[0]['even']); + + $factory->incrementLoopIndices(); + + $this->assertEquals(2, $factory->getLoopStack()[0]['iteration']); + $this->assertEquals(1, $factory->getLoopStack()[0]['index']); + $this->assertEquals(2, $factory->getLoopStack()[0]['remaining']); + $this->assertFalse($factory->getLoopStack()[0]['odd']); + $this->assertTrue($factory->getLoopStack()[0]['even']); + } + + public function testReachingEndOfLoop() + { + $factory = $this->getFactory(); + + $factory->addLoop([1, 2]); + + $factory->incrementLoopIndices(); + + $factory->incrementLoopIndices(); + + $this->assertTrue($factory->getLoopStack()[0]['last']); + } + + public function testIncrementingLoopIndicesOfUncountable() + { + $factory = $this->getFactory(); + + $factory->addLoop(''); + + $factory->incrementLoopIndices(); + + $factory->incrementLoopIndices(); + + $this->assertEquals(2, $factory->getLoopStack()[0]['iteration']); + $this->assertEquals(1, $factory->getLoopStack()[0]['index']); + $this->assertFalse($factory->getLoopStack()[0]['first']); + $this->assertNull($factory->getLoopStack()[0]['remaining']); + $this->assertNull($factory->getLoopStack()[0]['last']); + } + + public function testMacro() + { + $factory = $this->getFactory(); + $factory->macro('getFoo', function () { + return 'Hello World'; + }); + $this->assertEquals('Hello World', $factory->getFoo()); + } + protected function getFactory() { return new Factory( - m::mock('Illuminate\View\Engines\EngineResolver'), - m::mock('Wpb\String_Blade_Compiler\ViewFinderInterface'), - m::mock('Illuminate\Contracts\Events\Dispatcher') + m::mock(EngineResolver::class), + m::mock(ViewFinderInterface::class), + m::mock(DispatcherContract::class) ); } protected function getFactoryArgs() { return [ - m::mock('Illuminate\View\Engines\EngineResolver'), - m::mock('Wpb\String_Blade_Compiler\ViewFinderInterface'), - m::mock('Illuminate\Contracts\Events\Dispatcher'), + m::mock(EngineResolver::class), + m::mock(ViewFinderInterface::class), + m::mock(DispatcherContract::class), ]; } } diff --git a/tests/View/ViewStringBladeCompilerTest.php b/tests/View/ViewStringBladeCompilerTest.php new file mode 100644 index 0000000..fc99c54 --- /dev/null +++ b/tests/View/ViewStringBladeCompilerTest.php @@ -0,0 +1,196 @@ +getFiles(), __DIR__); + $files->shouldReceive('exists')->once()->with(__DIR__.'/'.sha1('foo').'.php')->andReturn(false); + $this->assertTrue($compiler->isExpired((object)['cache_key' => 'foo'])); + } + + public function testCannotConstructWithBadCachePath() + { + /* StringBladeComplier can have a empty cache path */ + + //$this->expectException(InvalidArgumentException::class); + //$this->expectExceptionMessage('Please provide a valid cache path.'); + + //new BladeCompiler($this->getFiles(), null); + $this->assertTrue(true); + } + + public function testIsExpiredReturnsTrueWhenModificationTimesWarrant() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $files->shouldReceive('exists')->once()->with(__DIR__.'/'.sha1('foo').'.php')->andReturn(true); + //$files->shouldReceive('lastModified')->once()->with('foo')->andReturn(100); + $files->shouldReceive('lastModified')->once()->with(__DIR__.'/'.sha1('foo').'.php')->andReturn(0); + + $this->assertTrue($compiler->isExpired((object)['cache_key' => 'foo'])); + } + + public function testCompilePathIsProperlyCreated() + { + $compiler = new BladeCompiler($this->getFiles(), __DIR__); + $this->assertEquals(__DIR__.'/'.sha1('foo').'.php', $compiler->getCompiledPath((object)['cache_key' => 'foo'])); + } + + public function testCompileCompilesFileAndReturnsContents() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + //$files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); + $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('foo').'.php', 'Hello World'); + $compiler->compile((object)['template' => 'Hello World', 'templateRefKey' => 'foo', 'cache_key' => 'foo']); + } + + public function testCompileCompilesAndGetThePath() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + //$files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); + $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('foo').'.php', 'Hello World'); + $compiler->compile((object)['template' => 'Hello World', 'templateRefKey' => 'foo', 'cache_key' => 'foo']); + $this->assertEquals('foo', $compiler->getPath()); + } + + public function testCompileSetAndGetThePath() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $compiler->setPath('foo'); + $this->assertEquals('foo', $compiler->getPath()); + } + + public function testCompileWithPathSetBefore() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + //$files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); + $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('foo').'.php', 'Hello World'); + // set path before compilation + $compiler->setViewData((object)['template' => 'Hello World', 'templateRefKey' => 'foo', 'cache_key' => 'foo']); + // trigger compilation with $path + $compiler->compile(); + $this->assertEquals('foo', $compiler->getPath()); + } + + public function testRawTagsCanBeSetToLegacyValues() + { + $compiler = new BladeCompiler($this->getFiles(), __DIR__); + $compiler->setEchoFormat('%s'); + + $this->assertEquals('', $compiler->compileString('{{{ $name }}}')); + $this->assertEquals('', $compiler->compileString('{{ $name }}')); + $this->assertEquals('', $compiler->compileString('{{ + $name + }}')); + } + + /** + * @param string $content + * @param string $compiled + * + * @dataProvider appendViewPathDataProvider + */ + public function testIncludePathToTemplate($content, $compiled) + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + //$files->shouldReceive('get')->once()->with('foo')->andReturn($content); + $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('foo').'.php', $compiled); + + $compiler->compile((object)['template' => $content, 'templateRefKey' => 'foo', 'cache_key' => 'foo']); + } + + /** + * @return array + */ + public function appendViewPathDataProvider() + { + return [ + 'No PHP blocks' => [ + 'Hello World', + 'Hello World', + ], + 'Single PHP block without closing ?>' => [ + '', + ], + 'Ending PHP block.' => [ + 'Hello world', + 'Hello world', + ], + 'Ending PHP block without closing ?>' => [ + 'Hello world', + ], + 'PHP block between content.' => [ + 'Hello worldHi There', + 'Hello worldHi There', + ], + 'Multiple PHP blocks.' => [ + 'Hello worldHi ThereHello Again', + 'Hello worldHi ThereHello Again', + ], + 'Multiple PHP blocks without closing ?>' => [ + 'Hello worldHi ThereHi There', + ], + 'Short open echo tag' => [ + 'Hello world', + ], + 'Echo XML declaration' => [ + '\';', + '\'; ?>', + ], + ]; + } + + public function testDontIncludeEmptyPath() + { + /* Stupid test, you can't get a file without a file name */ + /* + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $files->shouldReceive('get')->once()->with('')->andReturn('Hello World'); + $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('').'.php', 'Hello World'); + $compiler->setPath(''); + $compiler->compile(); + */ + } + + public function testDontIncludeNullPath() + { + /* Stupid test, you can't get a file named null */ + /* + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $files->shouldReceive('get')->once()->with(null)->andReturn('Hello World'); + $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1(null).'.php', 'Hello World'); + $compiler->setPath(null); + $compiler->compile(); + */ + } + + public function testShouldStartFromStrictTypesDeclaration() + { + $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); + $strictTypeDecl = "assertTrue(substr($compiler->compileString("getView(); $view->with('foo', 'bar'); $view->with(['baz' => 'boom']); $this->assertEquals(['foo' => 'bar', 'baz' => 'boom'], $view->getData()); - $view = new View(m::mock('Wpb\String_Blade_Compiler\Factory'), m::mock('Illuminate\Contracts\View\Engine'), 'view', 'path', []); + $view = $this->getView(); $view->withFoo('bar')->withBaz('boom'); $this->assertEquals(['foo' => 'bar', 'baz' => 'boom'], $view->getData()); } public function testRenderProperlyRendersView() { - $view = $this->getView(); + $view = $this->getView(['foo' => 'bar']); $view->getFactory()->shouldReceive('incrementRender')->once()->ordered(); $view->getFactory()->shouldReceive('callComposer')->once()->ordered()->with($view); $view->getFactory()->shouldReceive('getShared')->once()->andReturn(['shared' => 'foo']); $view->getEngine()->shouldReceive('get')->once()->with('path', ['foo' => 'bar', 'shared' => 'foo'])->andReturn('contents'); $view->getFactory()->shouldReceive('decrementRender')->once()->ordered(); - $view->getFactory()->shouldReceive('flushSectionsIfDoneRendering')->once(); + $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->once(); - $me = $this; - $callback = function (View $rendered, $contents) use ($me, $view) { - $me->assertEquals($view, $rendered); - $me->assertEquals('contents', $contents); + $callback = function (View $rendered, $contents) use ($view) { + $this->assertEquals($view, $rendered); + $this->assertEquals('contents', $contents); }; + $this->assertEquals('contents', $view->render($callback)); } + public function testRenderHandlingCallbackReturnValues() + { + $view = $this->getView(); + $view->getFactory()->shouldReceive('incrementRender'); + $view->getFactory()->shouldReceive('callComposer'); + $view->getFactory()->shouldReceive('getShared')->andReturn(['shared' => 'foo']); + $view->getEngine()->shouldReceive('get')->andReturn('contents'); + $view->getFactory()->shouldReceive('decrementRender'); + $view->getFactory()->shouldReceive('flushStateIfDoneRendering'); + + $this->assertEquals('new contents', $view->render(function () { + return 'new contents'; + })); + + $this->assertEmpty($view->render(function () { + return ''; + })); + + $this->assertEquals('contents', $view->render(function () { + // + })); + } + public function testRenderSectionsReturnsEnvironmentSections() { - $view = m::mock('Wpb\String_Blade_Compiler\View[render]', [ - m::mock('Wpb\String_Blade_Compiler\Factory'), - m::mock('Illuminate\Contracts\View\Engine'), + $view = m::mock(View::class.'[render]', [ + m::mock(Factory::class), + m::mock(Engine::class), 'view', 'path', [], ]); - $view->shouldReceive('render')->with(m::type('Closure'))->once()->andReturn($sections = ['foo' => 'bar']); + $view->shouldReceive('render')->with(m::type(Closure::class))->once()->andReturn($sections = ['foo' => 'bar']); $this->assertEquals($sections, $view->renderSections()); } public function testSectionsAreNotFlushedWhenNotDoneRendering() { - $view = $this->getView(); + $view = $this->getView(['foo' => 'bar']); $view->getFactory()->shouldReceive('incrementRender')->twice(); $view->getFactory()->shouldReceive('callComposer')->twice()->with($view); $view->getFactory()->shouldReceive('getShared')->twice()->andReturn(['shared' => 'foo']); $view->getEngine()->shouldReceive('get')->twice()->with('path', ['foo' => 'bar', 'shared' => 'foo'])->andReturn('contents'); $view->getFactory()->shouldReceive('decrementRender')->twice(); - $view->getFactory()->shouldReceive('flushSectionsIfDoneRendering')->twice(); + $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->twice(); $this->assertEquals('contents', $view->render()); $this->assertEquals('contents', (string) $view); @@ -76,21 +111,15 @@ public function testViewNestBindsASubView() $view->getFactory()->shouldReceive('make')->once()->with('foo', ['data']); $result = $view->nest('key', 'foo', ['data']); - $this->assertInstanceOf('Wpb\String_Blade_Compiler\View', $result); + $this->assertInstanceOf(View::class, $result); } public function testViewAcceptsArrayableImplementations() { - $arrayable = m::mock('Illuminate\Contracts\Support\Arrayable'); + $arrayable = m::mock(Arrayable::class); $arrayable->shouldReceive('toArray')->once()->andReturn(['foo' => 'bar', 'baz' => ['qux', 'corge']]); - $view = new View( - m::mock('Wpb\String_Blade_Compiler\Factory'), - m::mock('Illuminate\Contracts\View\Engine'), - 'view', - 'path', - $arrayable - ); + $view = $this->getView($arrayable); $this->assertEquals('bar', $view->foo); $this->assertEquals(['qux', 'corge'], $view->baz); @@ -98,8 +127,8 @@ public function testViewAcceptsArrayableImplementations() public function testViewGettersSetters() { - $view = $this->getView(); - $this->assertEquals($view->getName(), 'view'); + $view = $this->getView(['foo' => 'bar']); + $this->assertEquals($view->name(), 'view'); $this->assertEquals($view->getPath(), 'path'); $data = $view->getData(); $this->assertEquals($data['foo'], 'bar'); @@ -109,8 +138,20 @@ public function testViewGettersSetters() public function testViewArrayAccess() { - $view = $this->getView(); - $this->assertInstanceOf('ArrayAccess', $view); + $view = $this->getView(['foo' => 'bar']); + $this->assertInstanceOf(ArrayAccess::class, $view); + $this->assertTrue($view->offsetExists('foo')); + $this->assertEquals($view->offsetGet('foo'), 'bar'); + $view->offsetSet('foo', 'baz'); + $this->assertEquals($view->offsetGet('foo'), 'baz'); + $view->offsetUnset('foo'); + $this->assertFalse($view->offsetExists('foo')); + } + + public function testViewConstructedWithObjectData() + { + $view = $this->getView(new DataObjectStub); + $this->assertInstanceOf(ArrayAccess::class, $view); $this->assertTrue($view->offsetExists('foo')); $this->assertEquals($view->offsetGet('foo'), 'bar'); $view->offsetSet('foo', 'baz'); @@ -121,7 +162,7 @@ public function testViewArrayAccess() public function testViewMagicMethods() { - $view = $this->getView(); + $view = $this->getView(['foo' => 'bar']); $this->assertTrue(isset($view->foo)); $this->assertEquals($view->foo, 'bar'); $view->foo = 'baz'; @@ -134,7 +175,9 @@ public function testViewMagicMethods() public function testViewBadMethod() { - $this->setExpectedException('BadMethodCallException'); + $this->expectException(BadMethodCallException::class); + $this->expectExceptionMessage('Method Wpb\String_Blade_Compiler\View::badMethodCall does not exist.'); + $view = $this->getView(); $view->badMethodCall(); } @@ -147,9 +190,9 @@ public function testViewGatherDataWithRenderable() $view->getFactory()->shouldReceive('getShared')->once()->andReturn(['shared' => 'foo']); $view->getEngine()->shouldReceive('get')->once()->andReturn('contents'); $view->getFactory()->shouldReceive('decrementRender')->once()->ordered(); - $view->getFactory()->shouldReceive('flushSectionsIfDoneRendering')->once(); + $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->once(); - $view->renderable = m::mock('Illuminate\Contracts\Support\Renderable'); + $view->renderable = m::mock(Renderable::class); $view->renderable->shouldReceive('render')->once()->andReturn('text'); $this->assertEquals('contents', $view->render()); } @@ -162,7 +205,7 @@ public function testViewRenderSections() $view->getFactory()->shouldReceive('getShared')->once()->andReturn(['shared' => 'foo']); $view->getEngine()->shouldReceive('get')->once()->andReturn('contents'); $view->getFactory()->shouldReceive('decrementRender')->once()->ordered(); - $view->getFactory()->shouldReceive('flushSectionsIfDoneRendering')->once(); + $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->once(); $view->getFactory()->shouldReceive('getSections')->once()->andReturn(['foo', 'bar']); $sections = $view->renderSections(); @@ -175,25 +218,26 @@ public function testWithErrors() $view = $this->getView(); $errors = ['foo' => 'bar', 'qu' => 'ux']; $this->assertSame($view, $view->withErrors($errors)); - $this->assertInstanceOf('Illuminate\Support\MessageBag', $view->errors); + $this->assertInstanceOf(MessageBag::class, $view->errors); $foo = $view->errors->get('foo'); $this->assertEquals($foo[0], 'bar'); $qu = $view->errors->get('qu'); $this->assertEquals($qu[0], 'ux'); $data = ['foo' => 'baz']; - $this->assertSame($view, $view->withErrors(new \Illuminate\Support\MessageBag($data))); + $this->assertSame($view, $view->withErrors(new MessageBag($data))); $foo = $view->errors->get('foo'); $this->assertEquals($foo[0], 'baz'); } - protected function getView() + protected function getView($data = []) { return new View( - m::mock('Wpb\String_Blade_Compiler\Factory'), - m::mock('Illuminate\Contracts\View\Engine'), + m::mock(Factory::class), + m::mock(Engine::class), 'view', 'path', - ['foo' => 'bar'] + $data ); } -} \ No newline at end of file +} + diff --git a/tests/View/fixtures/basic.php b/tests/View/fixtures/basic.php new file mode 100644 index 0000000..57abe57 --- /dev/null +++ b/tests/View/fixtures/basic.php @@ -0,0 +1,2 @@ + +Hello World diff --git a/tests/View/fixtures/component.php b/tests/View/fixtures/component.php new file mode 100644 index 0000000..4af443d --- /dev/null +++ b/tests/View/fixtures/component.php @@ -0,0 +1 @@ + diff --git a/tests/View/fixtures/namespaced/basic.php b/tests/View/fixtures/namespaced/basic.php new file mode 100644 index 0000000..557db03 --- /dev/null +++ b/tests/View/fixtures/namespaced/basic.php @@ -0,0 +1 @@ +Hello World diff --git a/tests/View/fixtures/nested/basic.php b/tests/View/fixtures/nested/basic.php new file mode 100644 index 0000000..557db03 --- /dev/null +++ b/tests/View/fixtures/nested/basic.php @@ -0,0 +1 @@ +Hello World diff --git a/tests/View/fixtures/nested/child.php b/tests/View/fixtures/nested/child.php new file mode 100644 index 0000000..ce0b1eb --- /dev/null +++ b/tests/View/fixtures/nested/child.php @@ -0,0 +1 @@ +Hello World diff --git a/tests/string_blade_compiler/fixtures/section-exception-layout.php b/tests/View/fixtures/section-exception-layout.php similarity index 100% rename from tests/string_blade_compiler/fixtures/section-exception-layout.php rename to tests/View/fixtures/section-exception-layout.php diff --git a/tests/string_blade_compiler/fixtures/section-exception.php b/tests/View/fixtures/section-exception.php similarity index 51% rename from tests/string_blade_compiler/fixtures/section-exception.php rename to tests/View/fixtures/section-exception.php index 13a8c90..e073d12 100644 --- a/tests/string_blade_compiler/fixtures/section-exception.php +++ b/tests/View/fixtures/section-exception.php @@ -1,4 +1,4 @@ -make('layout', array_except(get_defined_vars(), ['__data', '__path']))->render(); ?> +make('layout', \Illuminate\Support\Arr::except(get_defined_vars(), ['__data', '__path']))->render(); ?> startSection('content'); ?> stopSection(); ?> diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..84c3e68 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,32 @@ +getFiles(), __DIR__); - $files->shouldReceive('exists')->once()->with(__DIR__.'/'.md5('foo'))->andReturn(false); - $this->assertTrue($compiler->isExpired('foo')); - } - - public function testIsExpiredReturnsTrueIfCachePathIsNull() - { - $compiler = new BladeCompiler($files = $this->getFiles(), null); - $files->shouldReceive('exists')->never(); - $this->assertTrue($compiler->isExpired('foo')); - } - - public function testIsExpiredReturnsTrueWhenModificationTimesWarrant() - { - $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); - $files->shouldReceive('exists')->once()->with(__DIR__.'/'.md5('foo'))->andReturn(true); - $files->shouldReceive('lastModified')->once()->with('foo')->andReturn(100); - $files->shouldReceive('lastModified')->once()->with(__DIR__.'/'.md5('foo'))->andReturn(0); - $this->assertTrue($compiler->isExpired('foo')); - } - - public function testCompilePathIsProperlyCreated() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals(__DIR__.'/'.md5('foo'), $compiler->getCompiledPath('foo')); - } - - public function testCompileCompilesFileAndReturnsContents() - { - $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); - $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); - $files->shouldReceive('put')->once()->with(__DIR__.'/'.md5('foo'), 'Hello World'); - $compiler->compile('foo'); - } - - public function testCompileCompilesAndGetThePath() - { - $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); - $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); - $files->shouldReceive('put')->once()->with(__DIR__.'/'.md5('foo'), 'Hello World'); - $compiler->compile('foo'); - $this->assertEquals('foo', $compiler->getPath()); - } - - public function testCompileSetAndGetThePath() - { - $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); - $compiler->setPath('foo'); - $this->assertEquals('foo', $compiler->getPath()); - } - - public function testCompileWithPathSetBefore() - { - $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__); - $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); - $files->shouldReceive('put')->once()->with(__DIR__.'/'.md5('foo'), 'Hello World'); - // set path before compilation - $compiler->setPath('foo'); - // trigger compilation with null $path - $compiler->compile(); - $this->assertEquals('foo', $compiler->getPath()); - } - - public function testCompileDoesntStoreFilesWhenCachePathIsNull() - { - $compiler = new BladeCompiler($files = $this->getFiles(), null); - $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World'); - $files->shouldReceive('put')->never(); - $compiler->compile('foo'); - } - - public function testEchosAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - - $this->assertEquals('', $compiler->compileString('{!!$name!!}')); - $this->assertEquals('', $compiler->compileString('{!! $name !!}')); - $this->assertEquals('', $compiler->compileString('{!! - $name - !!}')); - $this->assertEquals('', $compiler->compileString('{!! $name or \'foo\' !!}')); - - $this->assertEquals('', $compiler->compileString('{{{$name}}}')); - $this->assertEquals('', $compiler->compileString('{{$name}}')); - $this->assertEquals('', $compiler->compileString('{{ $name }}')); - $this->assertEquals('', $compiler->compileString('{{ - $name - }}')); - $this->assertEquals("\n\n", $compiler->compileString("{{ \$name }}\n")); - $this->assertEquals("\r\n\r\n", $compiler->compileString("{{ \$name }}\r\n")); - $this->assertEquals("\n\n", $compiler->compileString("{{ \$name }}\n")); - $this->assertEquals("\r\n\r\n", $compiler->compileString("{{ \$name }}\r\n")); - - $this->assertEquals('', $compiler->compileString('{{ $name or "foo" }}')); - $this->assertEquals('name) ? $user->name : "foo"); ?>', $compiler->compileString('{{ $user->name or "foo" }}')); - $this->assertEquals('', $compiler->compileString('{{$name or "foo"}}')); - $this->assertEquals('', $compiler->compileString('{{ - $name or "foo" - }}')); - - $this->assertEquals('', $compiler->compileString('{{ $name or \'foo\' }}')); - $this->assertEquals('', $compiler->compileString('{{$name or \'foo\'}}')); - $this->assertEquals('', $compiler->compileString('{{ - $name or \'foo\' - }}')); - - $this->assertEquals('', $compiler->compileString('{{ $age or 90 }}')); - $this->assertEquals('', $compiler->compileString('{{$age or 90}}')); - $this->assertEquals('', $compiler->compileString('{{ - $age or 90 - }}')); - - $this->assertEquals('', $compiler->compileString('{{ "Hello world or foo" }}')); - $this->assertEquals('', $compiler->compileString('{{"Hello world or foo"}}')); - $this->assertEquals('', $compiler->compileString('{{$foo + $or + $baz}}')); - $this->assertEquals('', $compiler->compileString('{{ - "Hello world or foo" - }}')); - - $this->assertEquals('', $compiler->compileString('{{ \'Hello world or foo\' }}')); - $this->assertEquals('', $compiler->compileString('{{\'Hello world or foo\'}}')); - $this->assertEquals('', $compiler->compileString('{{ - \'Hello world or foo\' - }}')); - - $this->assertEquals('', $compiler->compileString('{{ myfunc(\'foo or bar\') }}')); - $this->assertEquals('', $compiler->compileString('{{ myfunc("foo or bar") }}')); - $this->assertEquals('', $compiler->compileString('{{ myfunc("$name or \'foo\'") }}')); - } - - public function testEscapedWithAtEchosAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals('{{$name}}', $compiler->compileString('@{{$name}}')); - $this->assertEquals('{{ $name }}', $compiler->compileString('@{{ $name }}')); - $this->assertEquals('{{ - $name - }}', - $compiler->compileString('@{{ - $name - }}')); - $this->assertEquals('{{ $name }} - ', - $compiler->compileString('@{{ $name }} - ')); - } - - public function testReversedEchosAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $compiler->setEscapedContentTags('{{', '}}'); - $compiler->setContentTags('{{{', '}}}'); - $this->assertEquals('', $compiler->compileString('{{$name}}')); - $this->assertEquals('', $compiler->compileString('{{{$name}}}')); - $this->assertEquals('', $compiler->compileString('{{{ $name }}}')); - $this->assertEquals('', $compiler->compileString('{{{ - $name - }}}')); - } - - public function testShortRawEchosAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $compiler->setRawTags('{{', '}}'); - $this->assertEquals('', $compiler->compileString('{{$name}}')); - $this->assertEquals('', $compiler->compileString('{{ $name }}')); - $this->assertEquals('', $compiler->compileString('{{{$name}}}')); - $this->assertEquals('', $compiler->compileString('{{{ $name }}}')); - } - - public function testExtendsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '@extends(\'foo\')'.PHP_EOL.'test'; - $expected = 'test'.PHP_EOL.'make(\'foo\', array_except(get_defined_vars(), array(\'__data\', \'__path\')))->render(); ?>'; - $this->assertEquals($expected, $compiler->compileString($string)); - - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '@extends(name(foo))'.PHP_EOL.'test'; - $expected = 'test'.PHP_EOL.'make(name(foo), array_except(get_defined_vars(), array(\'__data\', \'__path\')))->render(); ?>'; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testPushIsCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '@push(\'foo\') -test -@endpush'; - $expected = 'startSection(\'foo\'); ?> -test -appendSection(); ?>'; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testStackIsCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '@stack(\'foo\')'; - $expected = 'yieldContent(\'foo\'); ?>'; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testCommentsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '{{--this is a comment--}}'; - $expected = ''; - $this->assertEquals($expected, $compiler->compileString($string)); - - $string = '{{-- -this is a comment ---}}'; - $expected = ''; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testIfStatementsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '@if (name(foo(bar))) -breeze -@endif'; - $expected = ' -breeze -'; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testElseStatementsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '@if (name(foo(bar))) -breeze -@else -boom -@endif'; - $expected = ' -breeze - -boom -'; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testElseIfStatementsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '@if(name(foo(bar))) -breeze -@elseif(boom(breeze)) -boom -@endif'; - $expected = ' -breeze - -boom -'; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testUnlessStatementsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '@unless (name(foo(bar))) -breeze -@endunless'; - $expected = ' -breeze -'; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testWhileStatementsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '@while ($foo) -test -@endwhile'; - $expected = ' -test -'; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testNestedWhileStatementsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '@while ($foo) -@while ($bar) -test -@endwhile -@endwhile'; - $expected = ' - -test - -'; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testForStatementsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '@for ($i = 0; $i < 10; $i++) -test -@endfor'; - $expected = ' -test -'; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testNestedForStatementsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '@for ($i = 0; $i < 10; $i++) -@for ($j = 0; $j < 20; $j++) -test -@endfor -@endfor'; - $expected = ' - -test - -'; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testForeachStatementsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '@foreach ($this->getUsers() as $user) -test -@endforeach'; - $expected = 'getUsers() as $user): ?> -test -'; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testNestedForeachStatementsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '@foreach ($this->getUsers() as $user) -user info -@foreach ($user->tags as $tag) -tag info -@endforeach -@endforeach'; - $expected = 'getUsers() as $user): ?> -user info -tags as $tag): ?> -tag info - -'; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testForelseStatementsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '@forelse ($this->getUsers() as $user) -breeze -@empty -empty -@endforelse'; - $expected = 'getUsers() as $user): $__empty_1 = false; ?> -breeze - -empty -'; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testNestedForelseStatementsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '@forelse ($this->getUsers() as $user) -@forelse ($user->tags as $tag) -breeze -@empty -tag empty -@endforelse -@empty -empty -@endforelse'; - $expected = 'getUsers() as $user): $__empty_1 = false; ?> -tags as $tag): $__empty_2 = false; ?> -breeze - -tag empty - - -empty -'; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testStatementThatContainsNonConsecutiveParanthesisAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = "Foo @lang(function_call('foo(blah)')) bar"; - $expected = "Foo bar"; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testIncludesAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals('make(\'foo\', array_except(get_defined_vars(), array(\'__data\', \'__path\')))->render(); ?>', $compiler->compileString('@include(\'foo\')')); - $this->assertEquals('make(name(foo), array_except(get_defined_vars(), array(\'__data\', \'__path\')))->render(); ?>', $compiler->compileString('@include(name(foo))')); - } - - public function testShowEachAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals('renderEach(\'foo\', \'bar\'); ?>', $compiler->compileString('@each(\'foo\', \'bar\')')); - $this->assertEquals('renderEach(name(foo)); ?>', $compiler->compileString('@each(name(foo))')); - } - - public function testYieldsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals('yieldContent(\'foo\'); ?>', $compiler->compileString('@yield(\'foo\')')); - $this->assertEquals('yieldContent(\'foo\', \'bar\'); ?>', $compiler->compileString('@yield(\'foo\', \'bar\')')); - $this->assertEquals('yieldContent(name(foo)); ?>', $compiler->compileString('@yield(name(foo))')); - } - - public function testShowsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals('yieldSection(); ?>', $compiler->compileString('@show')); - } - - public function testLanguageAndChoicesAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals('', $compiler->compileString("@lang('foo')")); - $this->assertEquals('', $compiler->compileString("@choice('foo', 1)")); - } - - public function testSectionStartsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals('startSection(\'foo\'); ?>', $compiler->compileString('@section(\'foo\')')); - $this->assertEquals('startSection(name(foo)); ?>', $compiler->compileString('@section(name(foo))')); - } - - public function testStopSectionsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals('stopSection(); ?>', $compiler->compileString('@stop')); - } - - public function testOverwriteSectionsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals('stopSection(true); ?>', $compiler->compileString('@overwrite')); - } - - public function testEndSectionsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals('stopSection(); ?>', $compiler->compileString('@endsection')); - } - - public function testAppendSectionsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals('appendSection(); ?>', $compiler->compileString('@append')); - } - - public function testCustomPhpCodeIsCorrectlyHandled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals(' ', $compiler->compileString("@if(\$test) @endif")); - } - - public function testMixingYieldAndEcho() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals('yieldContent(\'title\'); ?> - ', $compiler->compileString("@yield('title') - {{Config::get('site.title')}}")); - } - - public function testCustomExtensionsAreCompiled() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $compiler->extend(function ($value) { return str_replace('foo', 'bar', $value); }); - $this->assertEquals('bar', $compiler->compileString('foo')); - } - - public function testCustomStatements() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $compiler->directive('customControl', function ($expression) { - return ""; - }); - - $string = '@if($foo) -@customControl(10, $foo, \'bar\') -@endif'; - $expected = ' - -'; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testCustomShortStatements() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $compiler->directive('customControl', function ($expression) { - return ''; - }); - - $string = '@customControl'; - $expected = ''; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - public function testConfiguringContentTags() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $compiler->setContentTags('[[', ']]'); - $compiler->setEscapedContentTags('[[[', ']]]'); - - $this->assertEquals('', $compiler->compileString('[[[ $name ]]]')); - $this->assertEquals('', $compiler->compileString('[[ $name ]]')); - $this->assertEquals('', $compiler->compileString('[[ - $name - ]]')); - } - - public function testRawTagsCanBeSetToLegacyValues() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $compiler->setContentTagsEscaped(false); - - $this->assertEquals('', $compiler->compileString('{{{ $name }}}')); - $this->assertEquals('', $compiler->compileString('{{ $name }}')); - $this->assertEquals('', $compiler->compileString('{{ - $name - }}')); - } - - public function testExpressionsOnTheSameLine() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals(' space () ', $compiler->compileString('@lang(foo(bar(baz(qux(breeze()))))) space () @lang(foo(bar))')); - } - - public function testExpressionWithinHTML() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals('>', $compiler->compileString('')); - $this->assertEquals('>', $compiler->compileString('')); - $this->assertEquals(' >', $compiler->compileString('')); - } - - protected function getFiles() - { - return m::mock('Illuminate\Filesystem\Filesystem'); - } - - public function testRetrieveDefaultContentTags() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals(['{{', '}}'], $compiler->getContentTags()); - } - - public function testRetrieveDefaultEscapedContentTags() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $this->assertEquals(['{{{', '}}}'], $compiler->getEscapedContentTags()); - } - - public function testSequentialCompileStringCalls() - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $string = '@extends(\'foo\')'.PHP_EOL.'test'; - $expected = 'test'.PHP_EOL.'make(\'foo\', array_except(get_defined_vars(), array(\'__data\', \'__path\')))->render(); ?>'; - $this->assertEquals($expected, $compiler->compileString($string)); - - // use the same compiler instance to compile another template with @extends directive - $string = '@extends(name(foo))'.PHP_EOL.'test'; - $expected = 'test'.PHP_EOL.'make(name(foo), array_except(get_defined_vars(), array(\'__data\', \'__path\')))->render(); ?>'; - $this->assertEquals($expected, $compiler->compileString($string)); - } - - /** - * @dataProvider testGetTagsProvider() - */ - public function testSetAndRetrieveContentTags($openingTag, $closingTag) - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $compiler->setContentTags($openingTag, $closingTag); - $this->assertSame([$openingTag, $closingTag], $compiler->getContentTags()); - } - - /** - * @dataProvider testGetTagsProvider() - */ - public function testSetAndRetrieveEscapedContentTags($openingTag, $closingTag) - { - $compiler = new BladeCompiler($this->getFiles(), __DIR__); - $compiler->setEscapedContentTags($openingTag, $closingTag); - $this->assertSame([$openingTag, $closingTag], $compiler->getEscapedContentTags()); - } - - public function testGetTagsProvider() - { - return [ - ['{{', '}}'], - ['{{{', '}}}'], - ['[[', ']]'], - ['[[[', ']]]'], - ['((', '))'], - ['(((', ')))'], - ]; - } -} diff --git a/tests/string_blade_compiler/ViewFileViewFinderTest.php b/tests/string_blade_compiler/ViewFileViewFinderTest.php deleted file mode 100644 index 807e5bf..0000000 --- a/tests/string_blade_compiler/ViewFileViewFinderTest.php +++ /dev/null @@ -1,142 +0,0 @@ -getFinder(); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.blade.php')->andReturn(true); - - $this->assertEquals(__DIR__.'/foo.blade.php', $finder->find('foo')); - } - - public function testCascadingFileLoading() - { - $finder = $this->getFinder(); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.blade.php')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.php')->andReturn(true); - - $this->assertEquals(__DIR__.'/foo.php', $finder->find('foo')); - } - - public function testDirectoryCascadingFileLoading() - { - $finder = $this->getFinder(); - $finder->addLocation(__DIR__.'/nested'); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.blade.php')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.php')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/nested/foo.blade.php')->andReturn(true); - - $this->assertEquals(__DIR__.'/nested/foo.blade.php', $finder->find('foo')); - } - - public function testNamespacedBasicFileLoading() - { - $finder = $this->getFinder(); - $finder->addNamespace('foo', __DIR__.'/foo'); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.blade.php')->andReturn(true); - - $this->assertEquals(__DIR__.'/foo/bar/baz.blade.php', $finder->find('foo::bar.baz')); - } - - public function testCascadingNamespacedFileLoading() - { - $finder = $this->getFinder(); - $finder->addNamespace('foo', __DIR__.'/foo'); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.blade.php')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.php')->andReturn(true); - - $this->assertEquals(__DIR__.'/foo/bar/baz.php', $finder->find('foo::bar.baz')); - } - - public function testDirectoryCascadingNamespacedFileLoading() - { - $finder = $this->getFinder(); - $finder->addNamespace('foo', [__DIR__.'/foo', __DIR__.'/bar']); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.blade.php')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo/bar/baz.php')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/bar/bar/baz.blade.php')->andReturn(true); - - $this->assertEquals(__DIR__.'/bar/bar/baz.blade.php', $finder->find('foo::bar.baz')); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testExceptionThrownWhenViewNotFound() - { - $finder = $this->getFinder(); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.blade.php')->andReturn(false); - $finder->getFilesystem()->shouldReceive('exists')->once()->with(__DIR__.'/foo.php')->andReturn(false); - - $finder->find('foo'); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testExceptionThrownOnInvalidViewName() - { - $finder = $this->getFinder(); - $finder->find('name::'); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testExceptionThrownWhenNoHintPathIsRegistered() - { - $finder = $this->getFinder(); - $finder->find('name::foo'); - } - - public function testAddingExtensionPrependsNotAppends() - { - $finder = $this->getFinder(); - $finder->addExtension('baz'); - $extensions = $finder->getExtensions(); - $this->assertEquals('baz', reset($extensions)); - } - - public function testAddingExtensionsReplacesOldOnes() - { - $finder = $this->getFinder(); - $finder->addExtension('baz'); - $finder->addExtension('baz'); - - $this->assertCount(3, $finder->getExtensions()); - } - - public function testPassingViewWithHintReturnsTrue() - { - $finder = $this->getFinder(); - - $this->assertTrue($finder->hasHintInformation('hint::foo.bar')); - } - - public function testPassingViewWithoutHintReturnsFalse() - { - $finder = $this->getFinder(); - - $this->assertFalse($finder->hasHintInformation('foo.bar')); - } - - public function testPassingViewWithFalseHintReturnsFalse() - { - $finder = $this->getFinder(); - - $this->assertFalse($finder->hasHintInformation('::foo.bar')); - } - - protected function getFinder() - { - return new Wpb\String_Blade_Compiler\FileViewFinder(m::mock('Illuminate\Filesystem\Filesystem'), [__DIR__]); - } -}