diff --git a/app/Composers/ModuleComposer.php b/app/Composers/ModuleComposer.php new file mode 100644 index 000000000..f15c186d9 --- /dev/null +++ b/app/Composers/ModuleComposer.php @@ -0,0 +1,71 @@ +app = $app; + $this->manager = $manager; + } + + /** + * Bind data to the view. + * + * @param \Illuminate\Contracts\View\View $view + * + * @return void + */ + public function compose(View $view) + { + $key = $view->getName(); + + $view->with('view', $key); + + $modules = "view.modules: {$key}"; + $groups = "view.groups: {$key}"; + + $modules = $this->app->bound($modules) ? $this->app[$modules] : []; + $groups = $this->app->bound($groups) ? $this->app[$groups] : []; + + $modules = $this->manager->groupModules($modules, $groups); + + $view->withModules($modules); + } +} diff --git a/app/Composers/StatusPageComposer.php b/app/Composers/Modules/ComponentsComposer.php similarity index 52% rename from app/Composers/StatusPageComposer.php rename to app/Composers/Modules/ComponentsComposer.php index 349fd24c9..a5df06ba5 100644 --- a/app/Composers/StatusPageComposer.php +++ b/app/Composers/Modules/ComponentsComposer.php @@ -9,40 +9,20 @@ * file that was distributed with this source code. */ -namespace CachetHQ\Cachet\Composers; +namespace CachetHQ\Cachet\Composers\Modules; -use CachetHQ\Cachet\Integrations\Core\System; use CachetHQ\Cachet\Models\Component; use CachetHQ\Cachet\Models\ComponentGroup; -use CachetHQ\Cachet\Models\Incident; use Illuminate\Contracts\View\View; /** * This is the status page composer. * * @author James Brooks + * @author Connor S. Parks */ -class StatusPageComposer +class ComponentsComposer { - /** - * The system instance. - * - * @var \CachetHQ\Cachet\Integrations\Contracts\System - */ - protected $system; - - /** - * Create a new status page composer instance. - * - * @param \CachetHQ\Cachet\Integrations\Contracts\System $system - * - * @return void - */ - public function __construct(System $system) - { - $this->system = $system; - } - /** * Index page view composer. * @@ -52,19 +32,12 @@ class StatusPageComposer */ public function compose(View $view) { - $status = $this->system->getStatus(); - - // Scheduled maintenance code. - $scheduledMaintenance = Incident::scheduled()->orderBy('scheduled_at')->get(); - // Component & Component Group lists. $usedComponentGroups = Component::enabled()->where('group_id', '>', 0)->groupBy('group_id')->pluck('group_id'); $componentGroups = ComponentGroup::whereIn('id', $usedComponentGroups)->orderBy('order')->get(); $ungroupedComponents = Component::enabled()->where('group_id', 0)->orderBy('order')->orderBy('created_at')->get(); - $view->with($status) - ->withComponentGroups($componentGroups) - ->withUngroupedComponents($ungroupedComponents) - ->withScheduledMaintenance($scheduledMaintenance); + $view->withComponentGroups($componentGroups) + ->withUngroupedComponents($ungroupedComponents); } } diff --git a/app/Composers/MetricsComposer.php b/app/Composers/Modules/MetricsComposer.php similarity index 96% rename from app/Composers/MetricsComposer.php rename to app/Composers/Modules/MetricsComposer.php index 90b691356..83870afba 100644 --- a/app/Composers/MetricsComposer.php +++ b/app/Composers/Modules/MetricsComposer.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace CachetHQ\Cachet\Composers; +namespace CachetHQ\Cachet\Composers\Modules; use CachetHQ\Cachet\Models\Metric; use Illuminate\Contracts\Config\Repository; diff --git a/app/Composers/Modules/ScheduledComposer.php b/app/Composers/Modules/ScheduledComposer.php new file mode 100644 index 000000000..4dac2fe45 --- /dev/null +++ b/app/Composers/Modules/ScheduledComposer.php @@ -0,0 +1,38 @@ + + * @author Connor S. Parks + */ +class ScheduledComposer +{ + /** + * Index page view composer. + * + * @param \Illuminate\Contracts\View\View $view + * + * @return void + */ + public function compose(View $view) + { + $scheduledMaintenance = Incident::scheduled()->orderBy('scheduled_at')->get(); + + $view->withScheduledMaintenance($scheduledMaintenance); + } +} diff --git a/app/Composers/Modules/StatusComposer.php b/app/Composers/Modules/StatusComposer.php new file mode 100644 index 000000000..f507021e1 --- /dev/null +++ b/app/Composers/Modules/StatusComposer.php @@ -0,0 +1,55 @@ + + * @author Connor S. Parks + */ +class StatusComposer +{ + /** + * The system instance. + * + * @var \CachetHQ\Cachet\Integrations\Contracts\System + */ + protected $system; + + /** + * Create a new status page composer instance. + * + * @param \CachetHQ\Cachet\Integrations\Contracts\System $system + * + * @return void + */ + public function __construct(System $system) + { + $this->system = $system; + } + + /** + * Index page view composer. + * + * @param \Illuminate\Contracts\View\View $view + * + * @return void + */ + public function compose(View $view) + { + $view->with($this->system->getStatus()); + } +} diff --git a/app/Composers/Modules/TimelineComposer.php b/app/Composers/Modules/TimelineComposer.php new file mode 100644 index 000000000..b0772c3de --- /dev/null +++ b/app/Composers/Modules/TimelineComposer.php @@ -0,0 +1,35 @@ + + * @author Connor S. Parks + */ +class TimelineComposer +{ + /** + * Index page view composer. + * + * @param \Illuminate\Contracts\View\View $view + * + * @return void + */ + public function compose(View $view) + { + // ... + } +} diff --git a/app/Foundation/Providers/ComposerServiceProvider.php b/app/Foundation/Providers/ComposerServiceProvider.php index 7831d008b..876903d31 100644 --- a/app/Foundation/Providers/ComposerServiceProvider.php +++ b/app/Foundation/Providers/ComposerServiceProvider.php @@ -14,8 +14,12 @@ namespace CachetHQ\Cachet\Foundation\Providers; use CachetHQ\Cachet\Composers\AppComposer; use CachetHQ\Cachet\Composers\CurrentUserComposer; use CachetHQ\Cachet\Composers\DashboardComposer; -use CachetHQ\Cachet\Composers\MetricsComposer; -use CachetHQ\Cachet\Composers\StatusPageComposer; +use CachetHQ\Cachet\Composers\ModuleComposer; +use CachetHQ\Cachet\Composers\Modules\ComponentsComposer as ComponentsModuleComposer; +use CachetHQ\Cachet\Composers\Modules\MetricsComposer as MetricsModuleComposer; +use CachetHQ\Cachet\Composers\Modules\ScheduledComposer as ScheduledModuleComposer; +use CachetHQ\Cachet\Composers\Modules\StatusComposer as StatusModuleComposer; +use CachetHQ\Cachet\Composers\Modules\TimelineComposer as TimelineModuleComposer; use CachetHQ\Cachet\Composers\ThemeComposer; use CachetHQ\Cachet\Composers\TimezoneLocaleComposer; use Illuminate\Contracts\View\Factory; @@ -32,11 +36,16 @@ class ComposerServiceProvider extends ServiceProvider { $factory->composer('*', AppComposer::class); $factory->composer('*', CurrentUserComposer::class); - $factory->composer(['index'], MetricsComposer::class); - $factory->composer(['index', 'single-incident', 'subscribe', 'signup'], StatusPageComposer::class); $factory->composer(['index', 'single-incident', 'subscribe.*', 'signup', 'dashboard.settings.theme', 'emails.*'], ThemeComposer::class); $factory->composer('dashboard.*', DashboardComposer::class); $factory->composer(['setup', 'dashboard.settings.localization'], TimezoneLocaleComposer::class); + + $factory->composer('*', ModuleComposer::class); + $factory->composer('partials.modules.components', ComponentsModuleComposer::class); + $factory->composer('partials.modules.metrics', MetricsModuleComposer::class); + $factory->composer('partials.modules.scheduled', ScheduledModuleComposer::class); + $factory->composer('partials.modules.status', StatusModuleComposer::class); + $factory->composer('partials.modules.timeline', TimelineModuleComposer::class); } /** diff --git a/app/Foundation/Providers/ModuleServiceProvider.php b/app/Foundation/Providers/ModuleServiceProvider.php new file mode 100644 index 000000000..c997d1e70 --- /dev/null +++ b/app/Foundation/Providers/ModuleServiceProvider.php @@ -0,0 +1,93 @@ + [ + ['group' => 'messages', 'partial' => 'partials.modules.messages'], + ['group' => 'status', 'partial' => 'partials.modules.status'], + ['group' => 'components', 'partial' => 'partials.modules.components'], + ['group' => 'metrics', 'partial' => 'partials.modules.metrics'], + ['group' => 'scheduled', 'partial' => 'partials.modules.scheduled'], + ['group' => 'timeline', 'partial' => 'partials.modules.timeline'], + ], + ]; + + /** + * The group definitions. + * + * @var array + */ + protected $groups = [ + 'index' => [ + 'messages' => 10000, + 'status' => 20000, + 'components' => 30000, + 'metrics' => 40000, + 'scheduled' => 50000, + 'timeline' => 60000, + ], + ]; + + /** + * Boot the service provider. + * + * @param \Illuminate\View\Compilers\BladeCompiler $blade + */ + public function boot(BladeCompiler $blade) + { + $blade->directive('modules', function ($group = null) { + return sprintf( + 'call(\'%s@%s\', [ + \'factory\' => $__env, + \'data\' => array_except(get_defined_vars(), array(\'__data\', \'__path\')), + \'modules\' => $modules, + \'group\' => %s, + ]); ?>', + ModulesRenderer::class, + 'renderModules', + $group === null ? 'null' : $group + ); + }); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + foreach ($this->modules as $key => $modules) { + $this->app->singleton("view.modules: {$key}", function () use ($modules) { + return $modules; + }); + } + + foreach ($this->groups as $key => $groups) { + $this->app->singleton("view.groups: {$key}", function () use ($groups) { + return $groups; + }); + } + } +} diff --git a/app/Services/Modules/Manager.php b/app/Services/Modules/Manager.php new file mode 100644 index 000000000..61a276c68 --- /dev/null +++ b/app/Services/Modules/Manager.php @@ -0,0 +1,80 @@ + + */ +class Manager +{ + /** + * Groups the modules. + * + * @param array $modules + * @param array $groups + * + * @return array + */ + public function groupModules(array $modules, array $groups = []) + { + $grouped = []; + $length = count($modules); + foreach ($modules as $order => $module) { + if (!is_array($module) || empty($module['group'])) { + $order = array_get($module, 'order', PHP_INT_MAX - $length + $order); + + $grouped[] = [$module, 'order' => $order]; + + continue; + } + + $group = $module['group']; + if (empty($grouped[$group])) { + $grouped[$group] = [$module]; + + continue; + } + + $grouped[$group][] = $module; + } + + foreach ($groups as $group => $order) { + if (empty($grouped[$group])) { + continue; + } + + $grouped[$group]['order'] = $order; + } + + return $grouped; + } + + /** + * Orders the modules. + * + * @param array $modules + * + * @return array + */ + public function orderModules($modules) + { + $modules = array_numeric_sort($modules); + + foreach ($modules as $group => $subModules) { + $modules[$group] = array_numeric_sort($subModules); + } + + return $modules; + } +} diff --git a/app/Services/Modules/Renderer.php b/app/Services/Modules/Renderer.php new file mode 100644 index 000000000..b8fe3df33 --- /dev/null +++ b/app/Services/Modules/Renderer.php @@ -0,0 +1,75 @@ + + */ +class Renderer +{ + /** + * Render the modules. + * + * @param \Illuminate\Contracts\View\Factory $factory + * @param array $data + * @param array $modules + * @param string|null $group + * + * @return string + */ + public function renderModules(Factory $factory, array $data, array $modules, $group = null) + { + if ($group !== null) { + if (empty($modules[$group])) { + return ''; + } + + return $this->renderModulesGroup($factory, $data, $modules[$group]); + } + + return array_reduce( + array_numeric_sort($modules), + function ($reduce, $module) use ($factory, $data) { + return $reduce.$this->renderModulesGroup($factory, $data, $module); + }, + '' + ); + } + + /** + * Render a group of modules. + * + * @param \Illuminate\Contracts\View\Factory $factory + * @param array $data + * @param array $modules + * + * @return string + */ + protected function renderModulesGroup(Factory $factory, array $data, array $modules) + { + return array_reduce( + array_numeric_sort($modules), + function ($reduce, $module) use ($factory, $data) { + if (empty($module['partial'])) { + return $reduce; + } + + return $reduce.$factory->make($module['partial'], $data)->render(); + }, + '' + ); + } +} diff --git a/app/helpers.php b/app/helpers.php index cabc6a3fa..e8ffd41ed 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -115,3 +115,27 @@ if (!function_exists('color_contrast')) { return ($yiq >= 128) ? 'black' : 'white'; } } + +if (!function_exists('array_numeric_sort')) { + /** + * Numerically sort an array based on a specific key. + * + * @param array $array + * @param string $key + * + * @return array + */ + function array_numeric_sort(array $array = [], $key = 'order') + { + uasort($array, function ($a, $b) use ($key) { + $a = array_get($a, $key, PHP_INT_MAX); + $b = array_get($b, $key, PHP_INT_MAX); + + $default = PHP_MAJOR_VERSION < 7 ? 1 : 0; + + return $a < $b ? -1 : ($a === $b ? $default : 1); + }); + + return $array; + } +} diff --git a/config/app.php b/config/app.php index abf14dfab..a1f3fb811 100644 --- a/config/app.php +++ b/config/app.php @@ -184,8 +184,9 @@ return [ 'CachetHQ\Cachet\Foundation\Providers\ComposerServiceProvider', 'CachetHQ\Cachet\Foundation\Providers\ConsoleServiceProvider', 'CachetHQ\Cachet\Foundation\Providers\ConfigServiceProvider', - 'CachetHQ\Cachet\Foundation\Providers\IntegrationServiceProvider', 'CachetHQ\Cachet\Foundation\Providers\EventServiceProvider', + 'CachetHQ\Cachet\Foundation\Providers\IntegrationServiceProvider', + 'CachetHQ\Cachet\Foundation\Providers\ModuleServiceProvider', 'CachetHQ\Cachet\Foundation\Providers\RepositoryServiceProvider', 'CachetHQ\Cachet\Foundation\Providers\RouteServiceProvider', diff --git a/resources/views/index.blade.php b/resources/views/index.blade.php index bed06e339..7b541d0de 100644 --- a/resources/views/index.blade.php +++ b/resources/views/index.blade.php @@ -1,60 +1,5 @@ @extends('layout.master') @section('content') -
- @include('dashboard.partials.errors') -
- -
-
{{ $system_message }}
-
- -@include('partials.about-app') - -@if(!$component_groups->isEmpty() || !$ungrouped_components->isEmpty()) -
- @include('partials.components') -
-@endif - -@if($display_metrics && $app_graphs) -
- @include('partials.metrics') -
-@endif - -@if(!$scheduled_maintenance->isEmpty()) -
- @include('partials.schedule') -
-@endif - -@if($days_to_show > 0) -
-

{{ trans('cachet.incidents.past') }}

- @foreach($all_incidents as $date => $incidents) - @include('partials.incidents', [compact($date), compact($incidents)]) - @endforeach -
- - -@endif - + @modules @stop diff --git a/resources/views/partials/modules/components.blade.php b/resources/views/partials/modules/components.blade.php new file mode 100644 index 000000000..11261121b --- /dev/null +++ b/resources/views/partials/modules/components.blade.php @@ -0,0 +1,5 @@ +@if(!$component_groups->isEmpty() || !$ungrouped_components->isEmpty()) +
+ @include('partials.components') +
+@endif diff --git a/resources/views/partials/modules/messages.blade.php b/resources/views/partials/modules/messages.blade.php new file mode 100644 index 000000000..145dbb7f3 --- /dev/null +++ b/resources/views/partials/modules/messages.blade.php @@ -0,0 +1,3 @@ +
+ @include('dashboard.partials.errors') +
diff --git a/resources/views/partials/modules/metrics.blade.php b/resources/views/partials/modules/metrics.blade.php new file mode 100644 index 000000000..2cff0bc2e --- /dev/null +++ b/resources/views/partials/modules/metrics.blade.php @@ -0,0 +1,5 @@ +@if($display_metrics && $app_graphs) +
+ @include('partials.metrics') +
+@endif diff --git a/resources/views/partials/modules/scheduled.blade.php b/resources/views/partials/modules/scheduled.blade.php new file mode 100644 index 000000000..2c47ab0b4 --- /dev/null +++ b/resources/views/partials/modules/scheduled.blade.php @@ -0,0 +1,5 @@ +@if(!$scheduled_maintenance->isEmpty()) +
+ @include('partials.schedule') +
+@endif diff --git a/resources/views/partials/modules/status.blade.php b/resources/views/partials/modules/status.blade.php new file mode 100644 index 000000000..53d1a3788 --- /dev/null +++ b/resources/views/partials/modules/status.blade.php @@ -0,0 +1,3 @@ +
+
{{ $system_message }}
+
diff --git a/resources/views/partials/modules/timeline.blade.php b/resources/views/partials/modules/timeline.blade.php new file mode 100644 index 000000000..48ab9cb76 --- /dev/null +++ b/resources/views/partials/modules/timeline.blade.php @@ -0,0 +1,27 @@ +@if($days_to_show > 0) +
+

{{ trans('cachet.incidents.past') }}

+ @foreach($all_incidents as $date => $incidents) + @include('partials.incidents', [compact($date), compact($incidents)]) + @endforeach +
+ + +@endif diff --git a/tests/Foundation/Providers/ModuleServiceProviderTest.php b/tests/Foundation/Providers/ModuleServiceProviderTest.php new file mode 100644 index 000000000..b8ff49e67 --- /dev/null +++ b/tests/Foundation/Providers/ModuleServiceProviderTest.php @@ -0,0 +1,25 @@ + + */ +class ModuleServiceProviderTest extends AbstractTestCase +{ + use ServiceProviderTrait; +} diff --git a/tests/Services/Modules/ManagerTest.php b/tests/Services/Modules/ManagerTest.php new file mode 100644 index 000000000..c4a5e08ef --- /dev/null +++ b/tests/Services/Modules/ManagerTest.php @@ -0,0 +1,166 @@ + + */ +class ManagerTest extends AbstractTestCase +{ + public function testGroupNoModules() + { + $manager = m::mock('CachetHQ\Cachet\Services\Modules\Manager[groupModules]'); + $manager->shouldReceive('groupModules')->once()->passthru(); + + $grouped = $manager->groupModules([]); + + $this->assertSame([], $grouped); + } + + public function testGroupModulesNoGroups() + { + $manager = m::mock('CachetHQ\Cachet\Services\Modules\Manager[groupModules]'); + $manager->shouldReceive('groupModules')->once()->passthru(); + + $grouped = $manager->groupModules($this->getModules(), []); + + $this->assertSame($this->getGroupedModules(), $grouped); + } + + public function testGroupModules() + { + $manager = m::mock('CachetHQ\Cachet\Services\Modules\Manager[groupModules]'); + $manager->shouldReceive('groupModules')->once()->passthru(); + + $grouped = $manager->groupModules($this->getModules(), $this->getGroups()); + + $this->assertSame($this->getGroupedModulesWithOrders(), $grouped); + } + + public function testOrderModules() + { + $manager = m::mock('CachetHQ\Cachet\Services\Modules\Manager[orderModules]'); + $manager->shouldReceive('orderModules')->once()->passthru(); + + $ordered = $manager->orderModules($this->getGroupedModules()); + + $this->assertSame($this->getOrderedModules(), $ordered); + } + + public function testOrderModulesWithOrders() + { + $manager = m::mock('CachetHQ\Cachet\Services\Modules\Manager[orderModules]'); + $manager->shouldReceive('orderModules')->once()->passthru(); + + $ordered = $manager->orderModules($this->getGroupedModulesWithOrders()); + + $this->assertSame($this->getOrderedModulesWithOrders(), $ordered); + } + + protected function getModules() + { + return [ + ['group' => 'two', 'partial' => 'partials.two.a'], + ['partial' => 'partials.a', 'order' => 8], + ['group' => 'one', 'partial' => 'partials.one.c'], + ['partial' => 'partials.c'], + ['group' => 'one', 'partial' => 'partials.one.a', 'order' => 1], + ['group' => 'one', 'partial' => 'partials.one.b', 'order' => 2], + ['partial' => 'partials.b', 'order' => 15], + ]; + } + + protected function getGroups() + { + return [ + 'one' => 1, + 'two' => 2, + ]; + } + + protected function getGroupedModules() + { + return [ + 'two' => [ + ['group' => 'two', 'partial' => 'partials.two.a'], + ], + [['partial' => 'partials.a', 'order' => 8], 'order' => 8], + 'one' => [ + ['group' => 'one', 'partial' => 'partials.one.c'], + ['group' => 'one', 'partial' => 'partials.one.a', 'order' => 1], + ['group' => 'one', 'partial' => 'partials.one.b', 'order' => 2], + ], + [['partial' => 'partials.c'], 'order' => PHP_INT_MAX - 4], + [['partial' => 'partials.b', 'order' => 15], 'order' => 15], + ]; + } + + protected function getGroupedModulesWithOrders() + { + return [ + 'two' => [ + ['group' => 'two', 'partial' => 'partials.two.a'], + 'order' => 2, + ], + [['partial' => 'partials.a', 'order' => 8], 'order' => 8], + 'one' => [ + ['group' => 'one', 'partial' => 'partials.one.c'], + ['group' => 'one', 'partial' => 'partials.one.a', 'order' => 1], + ['group' => 'one', 'partial' => 'partials.one.b', 'order' => 2], + 'order' => 1, + ], + [['partial' => 'partials.c'], 'order' => PHP_INT_MAX - 4], + [['partial' => 'partials.b', 'order' => 15], 'order' => 15], + ]; + } + + protected function getOrderedModules() + { + return [ + [['partial' => 'partials.a', 'order' => 8], 'order' => 8], + 2 => [['partial' => 'partials.b', 'order' => 15], 'order' => 15], + 1 => [['partial' => 'partials.c'], 'order' => PHP_INT_MAX - 4], + 'two' => [ + ['group' => 'two', 'partial' => 'partials.two.a'], + ], + 'one' => [ + 1 => ['group' => 'one', 'partial' => 'partials.one.a', 'order' => 1], + 2 => ['group' => 'one', 'partial' => 'partials.one.b', 'order' => 2], + 0 => ['group' => 'one', 'partial' => 'partials.one.c'], + ], + ]; + } + + protected function getOrderedModulesWithOrders() + { + return [ + 'one' => [ + 1 => ['group' => 'one', 'partial' => 'partials.one.a', 'order' => 1], + 2 => ['group' => 'one', 'partial' => 'partials.one.b', 'order' => 2], + 0 => ['group' => 'one', 'partial' => 'partials.one.c'], + 'order' => 1, + ], + 'two' => [ + ['group' => 'two', 'partial' => 'partials.two.a'], + 'order' => 2, + ], + [['partial' => 'partials.a', 'order' => 8], 'order' => 8], + 2 => [['partial' => 'partials.b', 'order' => 15], 'order' => 15], + 1 => [['partial' => 'partials.c'], 'order' => PHP_INT_MAX - 4], + ]; + } +} diff --git a/tests/Services/Modules/RendererTest.php b/tests/Services/Modules/RendererTest.php new file mode 100644 index 000000000..46ca1dac6 --- /dev/null +++ b/tests/Services/Modules/RendererTest.php @@ -0,0 +1,143 @@ + + */ +class RendererTest extends AbstractTestCase +{ + public function testRenderNoModules() + { + $renderer = m::mock('CachetHQ\Cachet\Services\Modules\Renderer[renderModules]'); + $renderer->shouldReceive('renderModules')->once()->passthru(); + + $output = $renderer->renderModules( + $this->getFactory(), + [], + [], + null + ); + + $this->assertSame('', $output); + } + + public function testRenderNonExistentModulesGroup() + { + $renderer = m::mock('CachetHQ\Cachet\Services\Modules\Renderer[renderModules]'); + $renderer->shouldReceive('renderModules')->once()->passthru(); + + $output = $renderer->renderModules( + $this->getFactory(), + [], + $this->getModules(), + 'non-existent group' + ); + + $this->assertSame('', $output); + } + + public function testRenderModules() + { + $renderer = m::mock('CachetHQ\Cachet\Services\Modules\Renderer[renderModules]'); + $renderer->shouldReceive('renderModules')->once()->passthru(); + + $factory = $this->getFactory([ + 'partial.a', 'partial.b', 'partial.c', + 'partial.one.a', 'partial.one.b', 'partial.one.c', + 'partial.two.a', + 'partial.three.a', 'partial.three.b', + ]); + + $output = $renderer->renderModules( + $factory, + ['a' => 'b', 'c' => 'd'], + $this->getModules(), + null + ); + + $this->assertSame('partial.apartial.one.apartial.one.bpartial.one.cpartial.two.apartial.bpartial.cpartial.three.apartial.three.b', $output); + } + + public function testRenderModuleGroups() + { + $renderer = m::mock('CachetHQ\Cachet\Services\Modules\Renderer[renderModules]'); + $renderer->shouldReceive('renderModules')->once()->passthru(); + + $factory = $this->getFactory([ + 'partial.one.a', 'partial.one.b', 'partial.one.c', + ]); + + $output = $renderer->renderModules( + $factory, + ['a' => 'b', 'c' => 'd'], + $this->getModules(), + 'one' + ); + + $this->assertSame('partial.one.apartial.one.bpartial.one.c', $output); + } + + protected function getModules() + { + return [ + 'two' => [ + 'order' => 3, + ['partial' => 'partial.two.a'], + ], + [ + ['partial' => 'partial.c'], + ['partial' => 'partial.b', 'order' => 1], + ], + [ + 'order' => 1, + ['partial' => 'partial.a'], + ], + 'one' => [ + 'order' => 2, + ['partial' => 'partial.one.c'], + ['partial' => 'partial.one.a', 'order' => 1], + ['partial' => 'partial.one.b', 'order' => 2], + ], + 'three' => [ + ['partial' => 'partial.three.a'], + ['partial' => 'partial.three.b'], + ], + ]; + } + + protected function getFactory($views = []) + { + $factory = m::mock('Illuminate\View\Factory[make]', $this->getFactoryArgs()); + + foreach ($views as $view) { + $factory->shouldReceive('make')->once()->with($view, ['a' => 'b', 'c' => 'd'])->andReturn($mockView = m::mock('StdClass')); + $mockView->shouldReceive('render')->once()->andReturn($view); + } + + return $factory; + } + + protected function getFactoryArgs() + { + return [ + m::mock('Illuminate\View\Engines\EngineResolver'), + m::mock('Illuminate\View\ViewFinderInterface'), + m::mock('Illuminate\Contracts\Events\Dispatcher'), + ]; + } +}