ViewFactoryTest.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784
  1. <?php
  2. namespace Illuminate\Tests\View;
  3. use Closure;
  4. use ErrorException;
  5. use Illuminate\Container\Container;
  6. use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
  7. use Illuminate\Contracts\View\Engine;
  8. use Illuminate\Contracts\View\View as ViewContract;
  9. use Illuminate\Events\Dispatcher;
  10. use Illuminate\Filesystem\Filesystem;
  11. use Illuminate\Support\HtmlString;
  12. use Illuminate\View\Compilers\CompilerInterface;
  13. use Illuminate\View\Engines\CompilerEngine;
  14. use Illuminate\View\Engines\EngineResolver;
  15. use Illuminate\View\Engines\PhpEngine;
  16. use Illuminate\View\Factory;
  17. use Illuminate\View\View;
  18. use Illuminate\View\ViewFinderInterface;
  19. use InvalidArgumentException;
  20. use Mockery as m;
  21. use PHPUnit\Framework\TestCase;
  22. use ReflectionFunction;
  23. use stdClass;
  24. class ViewFactoryTest extends TestCase
  25. {
  26. protected function tearDown(): void
  27. {
  28. m::close();
  29. }
  30. public function testMakeCreatesNewViewInstanceWithProperPathAndEngine()
  31. {
  32. unset($_SERVER['__test.view']);
  33. $factory = $this->getFactory();
  34. $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn('path.php');
  35. $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('php')->andReturn($engine = m::mock(Engine::class));
  36. $factory->getFinder()->shouldReceive('addExtension')->once()->with('php');
  37. $factory->setDispatcher(new Dispatcher);
  38. $factory->creator('view', function ($view) {
  39. $_SERVER['__test.view'] = $view;
  40. });
  41. $factory->addExtension('php', 'php');
  42. $view = $factory->make('view', ['foo' => 'bar'], ['baz' => 'boom']);
  43. $this->assertSame($engine, $view->getEngine());
  44. $this->assertSame($_SERVER['__test.view'], $view);
  45. unset($_SERVER['__test.view']);
  46. }
  47. public function testExistsPassesAndFailsViews()
  48. {
  49. $factory = $this->getFactory();
  50. $factory->getFinder()->shouldReceive('find')->once()->with('foo')->andThrow(InvalidArgumentException::class);
  51. $factory->getFinder()->shouldReceive('find')->once()->with('bar')->andReturn('path.php');
  52. $this->assertFalse($factory->exists('foo'));
  53. $this->assertTrue($factory->exists('bar'));
  54. }
  55. public function testRenderingOnceChecks()
  56. {
  57. $factory = $this->getFactory();
  58. $this->assertFalse($factory->hasRenderedOnce('foo'));
  59. $factory->markAsRenderedOnce('foo');
  60. $this->assertTrue($factory->hasRenderedOnce('foo'));
  61. $factory->flushState();
  62. $this->assertFalse($factory->hasRenderedOnce('foo'));
  63. }
  64. public function testFirstCreatesNewViewInstanceWithProperPath()
  65. {
  66. unset($_SERVER['__test.view']);
  67. $factory = $this->getFactory();
  68. $factory->getFinder()->shouldReceive('find')->twice()->with('view')->andReturn('path.php');
  69. $factory->getFinder()->shouldReceive('find')->once()->with('bar')->andThrow(InvalidArgumentException::class);
  70. $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('php')->andReturn($engine = m::mock(Engine::class));
  71. $factory->getFinder()->shouldReceive('addExtension')->once()->with('php');
  72. $factory->setDispatcher(new Dispatcher);
  73. $factory->creator('view', function ($view) {
  74. $_SERVER['__test.view'] = $view;
  75. });
  76. $factory->addExtension('php', 'php');
  77. $view = $factory->first(['bar', 'view'], ['foo' => 'bar'], ['baz' => 'boom']);
  78. $this->assertInstanceOf(ViewContract::class, $view);
  79. $this->assertSame($engine, $view->getEngine());
  80. $this->assertSame($_SERVER['__test.view'], $view);
  81. unset($_SERVER['__test.view']);
  82. }
  83. public function testFirstThrowsInvalidArgumentExceptionIfNoneFound()
  84. {
  85. $this->expectException(InvalidArgumentException::class);
  86. $factory = $this->getFactory();
  87. $factory->getFinder()->shouldReceive('find')->once()->with('view')->andThrow(InvalidArgumentException::class);
  88. $factory->getFinder()->shouldReceive('find')->once()->with('bar')->andThrow(InvalidArgumentException::class);
  89. $factory->getEngineResolver()->shouldReceive('resolve')->with('php')->andReturn($engine = m::mock(Engine::class));
  90. $factory->getFinder()->shouldReceive('addExtension')->with('php');
  91. $factory->addExtension('php', 'php');
  92. $factory->first(['bar', 'view'], ['foo' => 'bar'], ['baz' => 'boom']);
  93. }
  94. public function testRenderEachCreatesViewForEachItemInArray()
  95. {
  96. $factory = m::mock(Factory::class.'[make]', $this->getFactoryArgs());
  97. $factory->shouldReceive('make')->once()->with('foo', ['key' => 'bar', 'value' => 'baz'])->andReturn($mockView1 = m::mock(stdClass::class));
  98. $factory->shouldReceive('make')->once()->with('foo', ['key' => 'breeze', 'value' => 'boom'])->andReturn($mockView2 = m::mock(stdClass::class));
  99. $mockView1->shouldReceive('render')->once()->andReturn('dayle');
  100. $mockView2->shouldReceive('render')->once()->andReturn('rees');
  101. $result = $factory->renderEach('foo', ['bar' => 'baz', 'breeze' => 'boom'], 'value');
  102. $this->assertSame('daylerees', $result);
  103. }
  104. public function testEmptyViewsCanBeReturnedFromRenderEach()
  105. {
  106. $factory = m::mock(Factory::class.'[make]', $this->getFactoryArgs());
  107. $factory->shouldReceive('make')->once()->with('foo')->andReturn($mockView = m::mock(stdClass::class));
  108. $mockView->shouldReceive('render')->once()->andReturn('empty');
  109. $this->assertSame('empty', $factory->renderEach('view', [], 'iterator', 'foo'));
  110. }
  111. public function testRawStringsMayBeReturnedFromRenderEach()
  112. {
  113. $this->assertSame('foo', $this->getFactory()->renderEach('foo', [], 'item', 'raw|foo'));
  114. }
  115. public function testEnvironmentAddsExtensionWithCustomResolver()
  116. {
  117. $factory = $this->getFactory();
  118. $resolver = function () {
  119. //
  120. };
  121. $factory->getFinder()->shouldReceive('addExtension')->once()->with('foo');
  122. $factory->getEngineResolver()->shouldReceive('register')->once()->with('bar', $resolver);
  123. $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn('path.foo');
  124. $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('bar')->andReturn($engine = m::mock(Engine::class));
  125. $factory->getDispatcher()->shouldReceive('dispatch');
  126. $factory->addExtension('foo', 'bar', $resolver);
  127. $view = $factory->make('view', ['data']);
  128. $this->assertSame($engine, $view->getEngine());
  129. }
  130. public function testAddingExtensionPrependsNotAppends()
  131. {
  132. $factory = $this->getFactory();
  133. $factory->getFinder()->shouldReceive('addExtension')->once()->with('foo');
  134. $factory->addExtension('foo', 'bar');
  135. $extensions = $factory->getExtensions();
  136. $this->assertSame('bar', reset($extensions));
  137. $this->assertSame('foo', key($extensions));
  138. }
  139. public function testPrependedExtensionOverridesExistingExtensions()
  140. {
  141. $factory = $this->getFactory();
  142. $factory->getFinder()->shouldReceive('addExtension')->once()->with('foo');
  143. $factory->getFinder()->shouldReceive('addExtension')->once()->with('baz');
  144. $factory->addExtension('foo', 'bar');
  145. $factory->addExtension('baz', 'bar');
  146. $extensions = $factory->getExtensions();
  147. $this->assertSame('bar', reset($extensions));
  148. $this->assertSame('baz', key($extensions));
  149. }
  150. public function testComposersAreProperlyRegistered()
  151. {
  152. $factory = $this->getFactory();
  153. $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type(Closure::class));
  154. $callback = $factory->composer('foo', function () {
  155. return 'bar';
  156. });
  157. $callback = $callback[0];
  158. $this->assertSame('bar', $callback());
  159. }
  160. public function testComposersCanBeMassRegistered()
  161. {
  162. $factory = $this->getFactory();
  163. $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: bar', m::type(Closure::class));
  164. $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: qux', m::type(Closure::class));
  165. $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type(Closure::class));
  166. $composers = $factory->composers([
  167. 'foo' => 'bar',
  168. 'baz@baz' => ['qux', 'foo'],
  169. ]);
  170. $this->assertCount(3, $composers);
  171. $reflections = [
  172. new ReflectionFunction($composers[0]),
  173. new ReflectionFunction($composers[1]),
  174. ];
  175. $this->assertEquals(['class' => 'foo', 'method' => 'compose'], $reflections[0]->getStaticVariables());
  176. $this->assertEquals(['class' => 'baz', 'method' => 'baz'], $reflections[1]->getStaticVariables());
  177. }
  178. public function testClassCallbacks()
  179. {
  180. $factory = $this->getFactory();
  181. $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type(Closure::class));
  182. $factory->setContainer($container = m::mock(Container::class));
  183. $container->shouldReceive('make')->once()->with('FooComposer')->andReturn($composer = m::mock(stdClass::class));
  184. $composer->shouldReceive('compose')->once()->with('view')->andReturn('composed');
  185. $callback = $factory->composer('foo', 'FooComposer');
  186. $callback = $callback[0];
  187. $this->assertSame('composed', $callback('view'));
  188. }
  189. public function testClassCallbacksWithMethods()
  190. {
  191. $factory = $this->getFactory();
  192. $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type(Closure::class));
  193. $factory->setContainer($container = m::mock(Container::class));
  194. $container->shouldReceive('make')->once()->with('FooComposer')->andReturn($composer = m::mock(stdClass::class));
  195. $composer->shouldReceive('doComposer')->once()->with('view')->andReturn('composed');
  196. $callback = $factory->composer('foo', 'FooComposer@doComposer');
  197. $callback = $callback[0];
  198. $this->assertSame('composed', $callback('view'));
  199. }
  200. public function testCallComposerCallsProperEvent()
  201. {
  202. $factory = $this->getFactory();
  203. $view = m::mock(View::class);
  204. $view->shouldReceive('name')->once()->andReturn('name');
  205. $factory->getDispatcher()->shouldReceive('dispatch')->once()->with('composing: name', [$view]);
  206. $factory->callComposer($view);
  207. }
  208. public function testComposersAreRegisteredWithSlashAndDot()
  209. {
  210. $factory = $this->getFactory();
  211. $factory->getDispatcher()->shouldReceive('listen')->with('composing: foo.bar', m::any())->twice();
  212. $factory->composer('foo.bar', '');
  213. $factory->composer('foo/bar', '');
  214. }
  215. public function testRenderCountHandling()
  216. {
  217. $factory = $this->getFactory();
  218. $factory->incrementRender();
  219. $this->assertFalse($factory->doneRendering());
  220. $factory->decrementRender();
  221. $this->assertTrue($factory->doneRendering());
  222. }
  223. public function testYieldDefault()
  224. {
  225. $factory = $this->getFactory();
  226. $this->assertSame('hi', $factory->yieldContent('foo', 'hi'));
  227. }
  228. public function testYieldDefaultIsEscaped()
  229. {
  230. $factory = $this->getFactory();
  231. $this->assertSame('&lt;p&gt;hi&lt;/p&gt;', $factory->yieldContent('foo', '<p>hi</p>'));
  232. }
  233. public function testYieldDefaultViewIsNotEscapedTwice()
  234. {
  235. $factory = $this->getFactory();
  236. $view = m::mock(View::class);
  237. $view->shouldReceive('__toString')->once()->andReturn('<p>hi</p>&lt;p&gt;already escaped&lt;/p&gt;');
  238. $this->assertSame('<p>hi</p>&lt;p&gt;already escaped&lt;/p&gt;', $factory->yieldContent('foo', $view));
  239. }
  240. public function testBasicSectionHandling()
  241. {
  242. $factory = $this->getFactory();
  243. $factory->startSection('foo');
  244. echo 'hi';
  245. $factory->stopSection();
  246. $this->assertSame('hi', $factory->yieldContent('foo'));
  247. }
  248. public function testBasicSectionDefault()
  249. {
  250. $factory = $this->getFactory();
  251. $factory->startSection('foo', 'hi');
  252. $this->assertSame('hi', $factory->yieldContent('foo'));
  253. }
  254. public function testBasicSectionDefaultIsEscaped()
  255. {
  256. $factory = $this->getFactory();
  257. $factory->startSection('foo', '<p>hi</p>');
  258. $this->assertSame('&lt;p&gt;hi&lt;/p&gt;', $factory->yieldContent('foo'));
  259. }
  260. public function testBasicSectionDefaultViewIsNotEscapedTwice()
  261. {
  262. $factory = $this->getFactory();
  263. $view = m::mock(View::class);
  264. $view->shouldReceive('__toString')->once()->andReturn('<p>hi</p>&lt;p&gt;already escaped&lt;/p&gt;');
  265. $factory->startSection('foo', $view);
  266. $this->assertSame('<p>hi</p>&lt;p&gt;already escaped&lt;/p&gt;', $factory->yieldContent('foo'));
  267. }
  268. public function testSectionExtending()
  269. {
  270. $placeholder = Factory::parentPlaceholder('foo');
  271. $factory = $this->getFactory();
  272. $factory->startSection('foo');
  273. echo 'hi '.$placeholder;
  274. $factory->stopSection();
  275. $factory->startSection('foo');
  276. echo 'there';
  277. $factory->stopSection();
  278. $this->assertSame('hi there', $factory->yieldContent('foo'));
  279. }
  280. public function testSectionMultipleExtending()
  281. {
  282. $placeholder = Factory::parentPlaceholder('foo');
  283. $factory = $this->getFactory();
  284. $factory->startSection('foo');
  285. echo 'hello '.$placeholder.' nice to see you '.$placeholder;
  286. $factory->stopSection();
  287. $factory->startSection('foo');
  288. echo 'my '.$placeholder;
  289. $factory->stopSection();
  290. $factory->startSection('foo');
  291. echo 'friend';
  292. $factory->stopSection();
  293. $this->assertSame('hello my friend nice to see you my friend', $factory->yieldContent('foo'));
  294. }
  295. public function testComponentHandling()
  296. {
  297. $factory = $this->getFactory();
  298. $factory->getFinder()->shouldReceive('find')->andReturn(__DIR__.'/fixtures/component.php');
  299. $factory->getEngineResolver()->shouldReceive('resolve')->andReturn(new PhpEngine(new Filesystem));
  300. $factory->getDispatcher()->shouldReceive('dispatch');
  301. $factory->startComponent('component', ['name' => 'Taylor']);
  302. $factory->slot('title');
  303. $factory->slot('website', 'laravel.com', []);
  304. echo 'title<hr>';
  305. $factory->endSlot();
  306. echo 'component';
  307. $contents = $factory->renderComponent();
  308. $this->assertSame('title<hr> component Taylor laravel.com', $contents);
  309. }
  310. public function testComponentHandlingUsingViewObject()
  311. {
  312. $factory = $this->getFactory();
  313. $factory->getFinder()->shouldReceive('find')->andReturn(__DIR__.'/fixtures/component.php');
  314. $factory->getEngineResolver()->shouldReceive('resolve')->andReturn(new PhpEngine(new Filesystem));
  315. $factory->getDispatcher()->shouldReceive('dispatch');
  316. $factory->startComponent($factory->make('component'), ['name' => 'Taylor']);
  317. $factory->slot('title');
  318. $factory->slot('website', 'laravel.com', []);
  319. echo 'title<hr>';
  320. $factory->endSlot();
  321. echo 'component';
  322. $contents = $factory->renderComponent();
  323. $this->assertSame('title<hr> component Taylor laravel.com', $contents);
  324. }
  325. public function testComponentHandlingUsingClosure()
  326. {
  327. $factory = $this->getFactory();
  328. $factory->getFinder()->shouldReceive('find')->andReturn(__DIR__.'/fixtures/component.php');
  329. $factory->getEngineResolver()->shouldReceive('resolve')->andReturn(new PhpEngine(new Filesystem));
  330. $factory->getDispatcher()->shouldReceive('dispatch');
  331. $factory->startComponent(function ($data) use ($factory) {
  332. $this->assertArrayHasKey('name', $data);
  333. $this->assertSame($data['name'], 'Taylor');
  334. return $factory->make('component');
  335. }, ['name' => 'Taylor']);
  336. $factory->slot('title');
  337. $factory->slot('website', 'laravel.com', []);
  338. echo 'title<hr>';
  339. $factory->endSlot();
  340. echo 'component';
  341. $contents = $factory->renderComponent();
  342. $this->assertSame('title<hr> component Taylor laravel.com', $contents);
  343. }
  344. public function testComponentHandlingUsingHtmlable()
  345. {
  346. $factory = $this->getFactory();
  347. $factory->startComponent(new HtmlString('laravel.com'));
  348. $contents = $factory->renderComponent();
  349. $this->assertSame('laravel.com', $contents);
  350. }
  351. public function testTranslation()
  352. {
  353. $container = new Container;
  354. $container->instance('translator', $translator = m::mock(stdClass::class));
  355. $translator->shouldReceive('get')->with('Foo', ['name' => 'taylor'])->andReturn('Bar');
  356. $factory = $this->getFactory();
  357. $factory->setContainer($container);
  358. $factory->startTranslation(['name' => 'taylor']);
  359. echo 'Foo';
  360. $string = $factory->renderTranslation();
  361. $this->assertSame('Bar', $string);
  362. }
  363. public function testSingleStackPush()
  364. {
  365. $factory = $this->getFactory();
  366. $factory->startPush('foo');
  367. echo 'hi';
  368. $factory->stopPush();
  369. $this->assertSame('hi', $factory->yieldPushContent('foo'));
  370. }
  371. public function testMultipleStackPush()
  372. {
  373. $factory = $this->getFactory();
  374. $factory->startPush('foo');
  375. echo 'hi';
  376. $factory->stopPush();
  377. $factory->startPush('foo');
  378. echo ', Hello!';
  379. $factory->stopPush();
  380. $this->assertSame('hi, Hello!', $factory->yieldPushContent('foo'));
  381. }
  382. public function testSingleStackPrepend()
  383. {
  384. $factory = $this->getFactory();
  385. $factory->startPrepend('foo');
  386. echo 'hi';
  387. $factory->stopPrepend();
  388. $this->assertSame('hi', $factory->yieldPushContent('foo'));
  389. }
  390. public function testMultipleStackPrepend()
  391. {
  392. $factory = $this->getFactory();
  393. $factory->startPrepend('foo');
  394. echo ', Hello!';
  395. $factory->stopPrepend();
  396. $factory->startPrepend('foo');
  397. echo 'hi';
  398. $factory->stopPrepend();
  399. $this->assertSame('hi, Hello!', $factory->yieldPushContent('foo'));
  400. }
  401. public function testSessionAppending()
  402. {
  403. $factory = $this->getFactory();
  404. $factory->startSection('foo');
  405. echo 'hi';
  406. $factory->appendSection();
  407. $factory->startSection('foo');
  408. echo 'there';
  409. $factory->appendSection();
  410. $this->assertSame('hithere', $factory->yieldContent('foo'));
  411. }
  412. public function testYieldSectionStopsAndYields()
  413. {
  414. $factory = $this->getFactory();
  415. $factory->startSection('foo');
  416. echo 'hi';
  417. $this->assertSame('hi', $factory->yieldSection());
  418. }
  419. public function testInjectStartsSectionWithContent()
  420. {
  421. $factory = $this->getFactory();
  422. $factory->inject('foo', 'hi');
  423. $this->assertSame('hi', $factory->yieldContent('foo'));
  424. }
  425. public function testEmptyStringIsReturnedForNonSections()
  426. {
  427. $factory = $this->getFactory();
  428. $this->assertEmpty($factory->yieldContent('foo'));
  429. }
  430. public function testSectionFlushing()
  431. {
  432. $factory = $this->getFactory();
  433. $factory->startSection('foo');
  434. echo 'hi';
  435. $factory->stopSection();
  436. $this->assertCount(1, $factory->getSections());
  437. $factory->flushSections();
  438. $this->assertCount(0, $factory->getSections());
  439. }
  440. public function testHasSection()
  441. {
  442. $factory = $this->getFactory();
  443. $factory->startSection('foo');
  444. echo 'hi';
  445. $factory->stopSection();
  446. $this->assertTrue($factory->hasSection('foo'));
  447. $this->assertFalse($factory->hasSection('bar'));
  448. }
  449. public function testSectionMissing()
  450. {
  451. $factory = $this->getFactory();
  452. $factory->startSection('foo');
  453. echo 'hello world';
  454. $factory->stopSection();
  455. $this->assertTrue($factory->sectionMissing('bar'));
  456. $this->assertFalse($factory->sectionMissing('foo'));
  457. }
  458. public function testGetSection()
  459. {
  460. $factory = $this->getFactory();
  461. $factory->startSection('foo');
  462. echo 'hi';
  463. $factory->stopSection();
  464. $this->assertSame('hi', $factory->getSection('foo'));
  465. $this->assertNull($factory->getSection('bar'));
  466. $this->assertSame('default', $factory->getSection('bar', 'default'));
  467. }
  468. public function testMakeWithSlashAndDot()
  469. {
  470. $factory = $this->getFactory();
  471. $factory->getFinder()->shouldReceive('find')->twice()->with('foo.bar')->andReturn('path.php');
  472. $factory->getEngineResolver()->shouldReceive('resolve')->twice()->with('php')->andReturn(m::mock(Engine::class));
  473. $factory->getDispatcher()->shouldReceive('dispatch');
  474. $factory->make('foo/bar');
  475. $factory->make('foo.bar');
  476. }
  477. public function testNamespacedViewNamesAreNormalizedProperly()
  478. {
  479. $factory = $this->getFactory();
  480. $factory->getFinder()->shouldReceive('find')->twice()->with('vendor/package::foo.bar')->andReturn('path.php');
  481. $factory->getEngineResolver()->shouldReceive('resolve')->twice()->with('php')->andReturn(m::mock(Engine::class));
  482. $factory->getDispatcher()->shouldReceive('dispatch');
  483. $factory->make('vendor/package::foo/bar');
  484. $factory->make('vendor/package::foo.bar');
  485. }
  486. public function testExceptionIsThrownForUnknownExtension()
  487. {
  488. $this->expectException(InvalidArgumentException::class);
  489. $factory = $this->getFactory();
  490. $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn('view.foo');
  491. $factory->make('view');
  492. }
  493. public function testExceptionsInSectionsAreThrown()
  494. {
  495. $this->expectException(ErrorException::class);
  496. $this->expectExceptionMessage('section exception message');
  497. $engine = new CompilerEngine(m::mock(CompilerInterface::class), new Filesystem);
  498. $engine->getCompiler()->shouldReceive('getCompiledPath')->andReturnUsing(function ($path) {
  499. return $path;
  500. });
  501. $engine->getCompiler()->shouldReceive('isExpired')->twice()->andReturn(false);
  502. $factory = $this->getFactory();
  503. $factory->getEngineResolver()->shouldReceive('resolve')->twice()->andReturn($engine);
  504. $factory->getFinder()->shouldReceive('find')->once()->with('layout')->andReturn(__DIR__.'/fixtures/section-exception-layout.php');
  505. $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn(__DIR__.'/fixtures/section-exception.php');
  506. $factory->getDispatcher()->shouldReceive('dispatch')->times(4);
  507. $factory->make('view')->render();
  508. }
  509. public function testExtraStopSectionCallThrowsException()
  510. {
  511. $this->expectException(InvalidArgumentException::class);
  512. $this->expectExceptionMessage('Cannot end a section without first starting one.');
  513. $factory = $this->getFactory();
  514. $factory->startSection('foo');
  515. $factory->stopSection();
  516. $factory->stopSection();
  517. }
  518. public function testExtraAppendSectionCallThrowsException()
  519. {
  520. $this->expectException(InvalidArgumentException::class);
  521. $this->expectExceptionMessage('Cannot end a section without first starting one.');
  522. $factory = $this->getFactory();
  523. $factory->startSection('foo');
  524. $factory->stopSection();
  525. $factory->appendSection();
  526. }
  527. public function testAddingLoops()
  528. {
  529. $factory = $this->getFactory();
  530. $factory->addLoop([1, 2, 3]);
  531. $expectedLoop = [
  532. 'iteration' => 0,
  533. 'index' => 0,
  534. 'remaining' => 3,
  535. 'count' => 3,
  536. 'first' => true,
  537. 'last' => false,
  538. 'odd' => false,
  539. 'even' => true,
  540. 'depth' => 1,
  541. 'parent' => null,
  542. ];
  543. $this->assertEquals([$expectedLoop], $factory->getLoopStack());
  544. $factory->addLoop([1, 2, 3, 4]);
  545. $secondExpectedLoop = [
  546. 'iteration' => 0,
  547. 'index' => 0,
  548. 'remaining' => 4,
  549. 'count' => 4,
  550. 'first' => true,
  551. 'last' => false,
  552. 'odd' => false,
  553. 'even' => true,
  554. 'depth' => 2,
  555. 'parent' => (object) $expectedLoop,
  556. ];
  557. $this->assertEquals([$expectedLoop, $secondExpectedLoop], $factory->getLoopStack());
  558. $factory->popLoop();
  559. $this->assertEquals([$expectedLoop], $factory->getLoopStack());
  560. }
  561. public function testAddingLoopDoesNotCloseGenerator()
  562. {
  563. $factory = $this->getFactory();
  564. $data = (new class
  565. {
  566. public function generate()
  567. {
  568. for ($count = 0; $count < 3; $count++) {
  569. yield ['a', 'b'];
  570. }
  571. }
  572. })->generate();
  573. $factory->addLoop($data);
  574. foreach ($data as $chunk) {
  575. $this->assertEquals(['a', 'b'], $chunk);
  576. }
  577. }
  578. public function testAddingUncountableLoop()
  579. {
  580. $factory = $this->getFactory();
  581. $factory->addLoop('');
  582. $expectedLoop = [
  583. 'iteration' => 0,
  584. 'index' => 0,
  585. 'remaining' => null,
  586. 'count' => null,
  587. 'first' => true,
  588. 'last' => null,
  589. 'odd' => false,
  590. 'even' => true,
  591. 'depth' => 1,
  592. 'parent' => null,
  593. ];
  594. $this->assertEquals([$expectedLoop], $factory->getLoopStack());
  595. }
  596. public function testIncrementingLoopIndices()
  597. {
  598. $factory = $this->getFactory();
  599. $factory->addLoop([1, 2, 3, 4]);
  600. $factory->incrementLoopIndices();
  601. $this->assertEquals(1, $factory->getLoopStack()[0]['iteration']);
  602. $this->assertEquals(0, $factory->getLoopStack()[0]['index']);
  603. $this->assertEquals(3, $factory->getLoopStack()[0]['remaining']);
  604. $this->assertTrue($factory->getLoopStack()[0]['odd']);
  605. $this->assertFalse($factory->getLoopStack()[0]['even']);
  606. $factory->incrementLoopIndices();
  607. $this->assertEquals(2, $factory->getLoopStack()[0]['iteration']);
  608. $this->assertEquals(1, $factory->getLoopStack()[0]['index']);
  609. $this->assertEquals(2, $factory->getLoopStack()[0]['remaining']);
  610. $this->assertFalse($factory->getLoopStack()[0]['odd']);
  611. $this->assertTrue($factory->getLoopStack()[0]['even']);
  612. }
  613. public function testReachingEndOfLoop()
  614. {
  615. $factory = $this->getFactory();
  616. $factory->addLoop([1, 2]);
  617. $factory->incrementLoopIndices();
  618. $factory->incrementLoopIndices();
  619. $this->assertTrue($factory->getLoopStack()[0]['last']);
  620. }
  621. public function testIncrementingLoopIndicesOfUncountable()
  622. {
  623. $factory = $this->getFactory();
  624. $factory->addLoop('');
  625. $factory->incrementLoopIndices();
  626. $factory->incrementLoopIndices();
  627. $this->assertEquals(2, $factory->getLoopStack()[0]['iteration']);
  628. $this->assertEquals(1, $factory->getLoopStack()[0]['index']);
  629. $this->assertFalse($factory->getLoopStack()[0]['first']);
  630. $this->assertNull($factory->getLoopStack()[0]['remaining']);
  631. $this->assertNull($factory->getLoopStack()[0]['last']);
  632. }
  633. public function testMacro()
  634. {
  635. $factory = $this->getFactory();
  636. $factory->macro('getFoo', function () {
  637. return 'Hello World';
  638. });
  639. $this->assertSame('Hello World', $factory->getFoo());
  640. }
  641. protected function getFactory()
  642. {
  643. return new Factory(
  644. m::mock(EngineResolver::class),
  645. m::mock(ViewFinderInterface::class),
  646. m::mock(DispatcherContract::class)
  647. );
  648. }
  649. protected function getFactoryArgs()
  650. {
  651. return [
  652. m::mock(EngineResolver::class),
  653. m::mock(ViewFinderInterface::class),
  654. m::mock(DispatcherContract::class),
  655. ];
  656. }
  657. }