implements a modular view system

This commit is contained in:
Connor S. Parks
2016-08-18 13:33:43 +01:00
parent 445c1ed2d0
commit fa1b32c11a
30 changed files with 809 additions and 181 deletions

View File

@@ -6,7 +6,6 @@ php:
- 5.6
- 7.0
- 7.1
- hhvm
sudo: false

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Composers;
use CachetHQ\Cachet\Services\Modules\Manager;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\View;
class ModuleComposer
{
/**
* The application instance.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The modules manager instance.
*
* @var \CachetHQ\Cachet\Services\Modules\Manager
*/
protected $manager;
/**
* Create a new modules composer.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \CachetHQ\Cachet\Services\Modules\Manager $manager
*
* @return void
*/
public function __construct(Application $app, Manager $manager)
{
$this->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);
}
}

View File

@@ -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 <james@alt-three.com>
* @author Connor S. Parks <connor@connorvg.tv>
*/
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);
}
}

View File

@@ -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;

View File

@@ -0,0 +1,38 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Composers\Modules;
use CachetHQ\Cachet\Models\Incident;
use Illuminate\Contracts\View\View;
/**
* This is the status page composer.
*
* @author James Brooks <james@alt-three.com>
* @author Connor S. Parks <connor@connorvg.tv>
*/
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);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Composers\Modules;
use CachetHQ\Cachet\Integrations\Contracts\System;
use Illuminate\Contracts\View\View;
/**
* This is the status page composer.
*
* @author James Brooks <james@alt-three.com>
* @author Connor S. Parks <connor@connorvg.tv>
*/
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());
}
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Composers\Modules;
use Illuminate\Contracts\View\View;
/**
* This is the status page composer.
*
* @author James Brooks <james@alt-three.com>
* @author Connor S. Parks <connor@connorvg.tv>
*/
class TimelineComposer
{
/**
* Index page view composer.
*
* @param \Illuminate\Contracts\View\View $view
*
* @return void
*/
public function compose(View $view)
{
// ...
}
}

View File

@@ -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);
}
/**

View File

@@ -11,8 +11,9 @@
namespace CachetHQ\Cachet\Foundation\Providers;
use Illuminate\View\Compilers\BladeCompiler;
use CachetHQ\Cachet\Services\Modules\Renderer as ModulesRenderer;
use Illuminate\Support\ServiceProvider;
use Illuminate\View\Compilers\BladeCompiler;
class ModuleServiceProvider extends ServiceProvider
{
@@ -44,7 +45,7 @@ class ModuleServiceProvider extends ServiceProvider
'components' => 30000,
'metrics' => 40000,
'scheduled' => 50000,
'timeline' => 60000,
'timeline' => 60000,
],
];
@@ -56,26 +57,17 @@ class ModuleServiceProvider extends ServiceProvider
public function boot(BladeCompiler $blade)
{
$blade->directive('modules', function ($group = null) {
$code = [
'foreach (array_numeric_sort($modules) as $group => $parts) {',
'foreach (array_numeric_sort($parts) as $part) {',
'if (empty($part[\'partial\'])) {',
'continue;',
'}',
'$params = array_except(get_defined_vars(), [\'__data\', \'__path\']);',
'$params = array_merge(array_get($part, \'parameters\', []), $params);',
'echo $__env->make($part[\'partial\'], $params)->render();',
'}',
'}',
];
if ($group) {
$code[0] = 'if (!empty($modules['.$group.'])) {'.PHP_EOL;
$code[0] .= '$parts = $modules['.$group.'];';
}
return '<?php '.implode(PHP_EOL, $code).'?>';
return sprintf(
'<?php echo $app->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
);
});
}

View File

@@ -0,0 +1,80 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Services\Modules;
/**
* This is the module manager class.
*
* @author Connor S. Parks <connor@connorvg.tv>
*/
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;
}
}

View File

@@ -0,0 +1,75 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Services\Modules;
use Illuminate\Contracts\View\Factory;
/**
* This is the module renderer class.
*
* @author Connor S. Parks <connor@connorvg.tv>
*/
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();
},
''
);
}
}

View File

@@ -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 && !defined('HHVM_VERSION') ? 1 : 0;
return $a < $b ? -1 : ($a === $b ? $default : 1);
});
return $array;
}
}

View File

@@ -31,8 +31,8 @@ $app = require_once __DIR__.'/bootstrap/app.php';
$kernel = $app->make('Illuminate\Contracts\Console\Kernel');
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
$input = new Symfony\Component\Console\Input\ArgvInput(),
new Symfony\Component\Console\Output\ConsoleOutput()
);
/*

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
{
"dist/css/all.css": "dist/css/all-8b09a6139d.css",
"dist/js/all.js": "dist/js/all-914268da61.js"
"dist/css/all.css": "dist/css/all-9f109896fb.css",
"dist/js/all.js": "dist/js/all-a2296565be.js"
}

View File

@@ -161,63 +161,38 @@ $(function() {
}
});
// Sortable components.
var componentList = document.getElementById("component-list");
if (componentList) {
new Sortable(componentList, {
group: "omega",
handle: ".drag-handle",
// Sortable models.
var orderableLists = document.querySelectorAll('[data-orderable-list]');
$.each(orderableLists, function (k, list) {
var url = $(list).data('orderableList');
var notifier = new Cachet.Notifier();
new Sortable(list, {
group: 'omega',
handle: '.drag-handle',
onUpdate: function() {
var orderedComponentIds = $.map($('#component-list .striped-list-item'), function(elem) {
return $(elem).data('component-id');
var orderedIds = $.map(list.querySelectorAll('[data-orderable-id]'), function(elem) {
return $(elem).data('orderable-id');
});
$.ajax({
async: true,
url: '/dashboard/api/components/order',
url: url,
type: 'POST',
data: {
ids: orderedComponentIds
ids: orderedIds
},
success: function() {
(new Cachet.Notifier()).notify('Component orders updated.', 'success');
notifier.notify('Ordering updated.', 'success');
},
error: function() {
(new Cachet.Notifier()).notify('Component orders not updated.', 'error');
notifier.notify('Ordering not updated.', 'error');
}
});
}
});
}
// Sortable Component Groups
var componentGroupList = document.getElementById("component-group-list");
if (componentGroupList) {
new Sortable(componentGroupList, {
group: "omega",
handle: ".drag-handle",
onUpdate: function() {
var orderedComponentGroupsIds = $.map(
$('#component-group-list .striped-list-item'),
function(elem) {
return $(elem).data('group-id');
}
);
$.ajax({
async: true,
url: '/dashboard/api/components/groups/order',
type: 'POST',
data: {ids: orderedComponentGroupsIds},
success: function() {
(new Cachet.Notifier()).notify('Component groups order has been updated.', 'success');
},
error: function() {
(new Cachet.Notifier()).notify('Component groups order could not be updated.', 'error');
}
});
}
});
}
// Toggle inline component statuses.
$('form.component-inline').on('click', 'input[type=radio]', function() {

View File

@@ -17,9 +17,9 @@
</div>
@include('dashboard.partials.errors')
<div class="row">
<div class="col-sm-12 striped-list" id="component-group-list">
<div class="col-sm-12 striped-list" data-orderable-list="/dashboard/api/components/groups/order">
@forelse($groups as $group)
<div class="row striped-list-item" data-group-id="{{ $group->id }}">
<div class="row striped-list-item" data-orderable-id="{{ $group->id }}">
<div class="col-xs-6">
<h4>
@if($groups->count() > 1)

View File

@@ -17,9 +17,9 @@
</div>
@include('dashboard.partials.errors')
<div class="row">
<div class="col-sm-12 striped-list" id="component-list">
<div class="col-sm-12 striped-list" data-orderable-list="/dashboard/api/components/order">
@forelse($components as $component)
<div class="row striped-list-item {{ !$component->enabled ? 'bg-warning' : null }}" data-component-id="{{ $component->id }}">
<div class="row striped-list-item {{ !$component->enabled ? 'bg-warning' : null }}" data-orderable-id="{{ $component->id }}">
<div class="col-xs-6">
<h4>
@if($components->count() > 1)

View File

@@ -1,60 +1,5 @@
@extends('layout.master')
@section('content')
<div class="section-messages">
@include('dashboard.partials.errors')
</div>
<div class="section-status">
<div class="alert alert-{{ $system_status }}">{{ $system_message }}</div>
</div>
@include('partials.about-app')
@if(!$component_groups->isEmpty() || !$ungrouped_components->isEmpty())
<div class="section-components">
@include('partials.components')
</div>
@endif
@if($display_metrics && $app_graphs)
<div class="section-metrics">
@include('partials.metrics')
</div>
@endif
@if(!$scheduled_maintenance->isEmpty())
<div class="section-scheduled">
@include('partials.schedule')
</div>
@endif
@if($days_to_show > 0)
<div class="section-timeline">
<h1>{{ trans('cachet.incidents.past') }}</h1>
@foreach($all_incidents as $date => $incidents)
@include('partials.incidents', [compact($date), compact($incidents)])
@endforeach
</div>
<nav>
<ul class="pager">
@if($can_page_backward)
<li class="previous">
<a href="{{ route('status-page') }}?start_date={{ $previous_date }}" class="links">
<span aria-hidden="true">&larr;</span> {{ trans('cachet.incidents.previous_week') }}
</a>
</li>
@endif
@if($can_page_forward)
<li class="next">
<a href="{{ route('status-page') }}?start_date={{ $next_date }}" class="links">
{{ trans('cachet.incidents.next_week') }} <span aria-hidden="true">&rarr;</span>
</a>
</li>
@endif
</ul>
</nav>
@endif
@modules
@stop

View File

@@ -0,0 +1,5 @@
@if(!$component_groups->isEmpty() || !$ungrouped_components->isEmpty())
<div class="section-components">
@include('partials.components')
</div>
@endif

View File

@@ -0,0 +1,3 @@
<div class="section-messages">
@include('dashboard.partials.errors')
</div>

View File

@@ -0,0 +1,5 @@
@if($display_metrics && $app_graphs)
<div class="section-metrics">
@include('partials.metrics')
</div>
@endif

View File

@@ -0,0 +1,5 @@
@if(!$scheduled_maintenance->isEmpty())
<div class="section-scheduled">
@include('partials.schedule')
</div>
@endif

View File

@@ -0,0 +1,3 @@
<div class="section-status">
<div class="alert alert-{{ $system_status }}">{{ $system_message }}</div>
</div>

View File

@@ -0,0 +1,27 @@
@if($days_to_show > 0)
<div class="section-timeline">
<h1>{{ trans('cachet.incidents.past') }}</h1>
@foreach($all_incidents as $date => $incidents)
@include('partials.incidents', [compact($date), compact($incidents)])
@endforeach
</div>
<nav>
<ul class="pager">
@if($can_page_backward)
<li class="previous">
<a href="{{ route('status-page') }}?start_date={{ $previous_date }}" class="links">
<span aria-hidden="true">&larr;</span> {{ trans('cachet.incidents.previous_week') }}
</a>
</li>
@endif
@if($can_page_forward)
<li class="next">
<a href="{{ route('status-page') }}?start_date={{ $next_date }}" class="links">
{{ trans('cachet.incidents.next_week') }} <span aria-hidden="true">&rarr;</span>
</a>
</li>
@endif
</ul>
</nav>
@endif

View File

@@ -0,0 +1,166 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Tests\Cachet\Services\Modules;
use CachetHQ\Tests\Cachet\AbstractTestCase;
use Mockery as m;
/**
* This is the modules manager service test class.
*
* @author Connor S. Parks <connor@connorvg.tv>
*/
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],
];
}
}

View File

@@ -0,0 +1,143 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Tests\Cachet\Services\Modules;
use CachetHQ\Tests\Cachet\AbstractTestCase;
use Mockery as m;
/**
* This is the modules renderer service test class.
*
* @author Connor S. Parks <connor@connorvg.tv>
*/
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'),
];
}
}