Some checks failed
Deploy Application / deploy (push) Has been cancelled
414 lines
14 KiB
PHP
414 lines
14 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Admin\AdminApiHandler;
|
|
use App\Framework\Admin\AdminPageRenderer;
|
|
use App\Framework\Admin\Factories\AdminFormFactory;
|
|
use App\Framework\Admin\Factories\AdminTableFactory;
|
|
use App\Framework\Admin\Factories\RepositoryAdapterFactory;
|
|
use App\Framework\Admin\Services\CrudService;
|
|
use App\Framework\Admin\ValueObjects\CrudConfig;
|
|
use App\Framework\Core\PathProvider;
|
|
use App\Framework\DI\DefaultContainer;
|
|
use App\Framework\Discovery\Results\AttributeRegistry;
|
|
use App\Framework\Discovery\Results\DiscoveryRegistry;
|
|
use App\Framework\Discovery\Results\InterfaceRegistry;
|
|
use App\Framework\Discovery\Results\TemplateRegistry;
|
|
use App\Framework\Http\Request;
|
|
use App\Framework\Http\Responses\Redirect;
|
|
use App\Framework\LiveComponents\ComponentCacheManager;
|
|
use App\Framework\LiveComponents\ComponentMetadataCache;
|
|
use App\Framework\LiveComponents\ComponentRegistry;
|
|
use App\Framework\LiveComponents\LiveComponentHandler;
|
|
use App\Framework\LiveComponents\Performance\NestedPerformanceTracker;
|
|
use App\Framework\Router\Result\ViewResult;
|
|
use App\Framework\View\Dom\Renderer\HtmlRenderer;
|
|
use App\Framework\View\LiveComponentRenderer;
|
|
use App\Framework\View\Loading\TemplateLoader;
|
|
use App\Framework\View\Table\Table;
|
|
use App\Framework\View\TemplateProcessor;
|
|
use App\Framework\Cache\Driver\NullCache;
|
|
use App\Framework\Cache\GeneralCache;
|
|
use App\Framework\Serializer\Serializer;
|
|
use App\Framework\DateTime\SystemClock;
|
|
use App\Framework\Http\Session\Session;
|
|
use App\Framework\Http\Session\SessionId;
|
|
use App\Framework\Random\SecureRandomGenerator;
|
|
use App\Framework\Security\CsrfTokenGenerator;
|
|
|
|
beforeEach(function () {
|
|
// Create minimal real instances for final classes
|
|
// Since many classes are final, we create real instances with minimal dependencies
|
|
$container = new DefaultContainer();
|
|
|
|
// Create DiscoveryRegistry
|
|
$discoveryRegistry = new DiscoveryRegistry(
|
|
attributes: new AttributeRegistry(),
|
|
interfaces: new InterfaceRegistry([]),
|
|
templates: new TemplateRegistry([])
|
|
);
|
|
|
|
// Create TemplateLoader for LiveComponentRenderer
|
|
$pathProvider = new PathProvider(__DIR__ . '/../../../../');
|
|
$nullCacheDriver = new NullCache();
|
|
$serializer = Mockery::mock(Serializer::class);
|
|
$serializer->shouldReceive('serialize')->andReturnUsing(fn($data) => serialize($data));
|
|
$serializer->shouldReceive('unserialize')->andReturnUsing(fn($data) => unserialize($data));
|
|
$cache = new GeneralCache($nullCacheDriver, $serializer);
|
|
$templateLoader = new TemplateLoader(
|
|
pathProvider: $pathProvider,
|
|
cache: $cache,
|
|
discoveryRegistry: null,
|
|
templates: [],
|
|
templatePath: '/src/Framework/View/templates',
|
|
cacheEnabled: false
|
|
);
|
|
|
|
// Create TemplateProcessor
|
|
$templateProcessor = new TemplateProcessor(
|
|
astTransformers: [],
|
|
stringProcessors: [],
|
|
container: $container,
|
|
chainOptimizer: null,
|
|
compiledTemplateCache: null,
|
|
performanceTracker: null
|
|
);
|
|
|
|
// Create real Session instance
|
|
$session = Session::fromArray(
|
|
SessionId::fromString(str_repeat('a', 32)),
|
|
new SystemClock(),
|
|
new CsrfTokenGenerator(new SecureRandomGenerator()),
|
|
[]
|
|
);
|
|
|
|
// Create LiveComponentRenderer
|
|
$liveComponentRenderer = new LiveComponentRenderer(
|
|
templateLoader: $templateLoader,
|
|
templateProcessor: $templateProcessor,
|
|
session: $session
|
|
);
|
|
|
|
// Create real instances for final classes with minimal dependencies
|
|
$cacheKeyBuilder = new \App\Framework\LiveComponents\Caching\CacheKeyBuilder();
|
|
$cacheManager = new ComponentCacheManager(
|
|
cache: $cache,
|
|
cacheKeyBuilder: $cacheKeyBuilder
|
|
);
|
|
|
|
$eventDispatcher = new \App\Framework\LiveComponents\ComponentEventDispatcher();
|
|
$handler = new LiveComponentHandler(
|
|
eventDispatcher: $eventDispatcher,
|
|
session: $session
|
|
);
|
|
|
|
$metadataCompiler = new \App\Framework\LiveComponents\Performance\ComponentMetadataCompiler();
|
|
$metadataCache = new ComponentMetadataCache(
|
|
cache: $cache,
|
|
compiler: $metadataCompiler
|
|
);
|
|
|
|
$highResClock = new \App\Framework\DateTime\HighResolutionClock();
|
|
$memoryMonitor = new \App\Framework\Performance\MemoryMonitor();
|
|
$performanceTracker = new NestedPerformanceTracker(
|
|
clock: new SystemClock(),
|
|
highResClock: $highResClock,
|
|
memoryMonitor: $memoryMonitor
|
|
);
|
|
|
|
// Create real ComponentRegistry
|
|
$componentRegistry = new ComponentRegistry(
|
|
container: $container,
|
|
discoveryRegistry: $discoveryRegistry,
|
|
renderer: $liveComponentRenderer,
|
|
cacheManager: $cacheManager,
|
|
handler: $handler,
|
|
metadataCache: $metadataCache,
|
|
performanceTracker: $performanceTracker
|
|
);
|
|
|
|
$this->pageRenderer = new AdminPageRenderer($componentRegistry);
|
|
$this->formFactory = Mockery::mock(AdminFormFactory::class);
|
|
$this->tableFactory = Mockery::mock(AdminTableFactory::class);
|
|
$apiHandler = new AdminApiHandler();
|
|
$adapterFactory = new RepositoryAdapterFactory();
|
|
$this->service = new CrudService(
|
|
$this->pageRenderer,
|
|
$this->formFactory,
|
|
$this->tableFactory,
|
|
$apiHandler,
|
|
$adapterFactory
|
|
);
|
|
|
|
$this->config = CrudConfig::forResource(
|
|
resource: 'campaigns',
|
|
resourceName: 'Campaign',
|
|
title: 'Campaigns'
|
|
)->withColumns([
|
|
['field' => 'name', 'label' => 'Name'],
|
|
['field' => 'status', 'label' => 'Status'],
|
|
]);
|
|
});
|
|
|
|
afterEach(function () {
|
|
Mockery::close();
|
|
});
|
|
|
|
describe('CrudService', function () {
|
|
describe('renderIndex', function () {
|
|
it('renders index view with items using AdminPageRenderer', function () {
|
|
$items = [
|
|
['id' => '1', 'name' => 'Campaign 1', 'status' => 'active'],
|
|
['id' => '2', 'name' => 'Campaign 2', 'status' => 'draft'],
|
|
];
|
|
|
|
$request = Mockery::mock(Request::class);
|
|
$request->shouldReceive('uri')->andReturn('/admin/campaigns');
|
|
|
|
$mockTable = Mockery::mock(Table::class);
|
|
$mockTable->shouldReceive('render')->andReturn('<table>...</table>');
|
|
|
|
$this->tableFactory->shouldReceive('create')
|
|
->once()
|
|
->andReturn($mockTable);
|
|
|
|
$result = $this->service->renderIndex(
|
|
$this->config,
|
|
$items,
|
|
$request
|
|
);
|
|
|
|
expect($result)->toBeInstanceOf(ViewResult::class);
|
|
expect($result->template)->toBe('admin-index');
|
|
});
|
|
|
|
it('includes create action when canCreate is true', function () {
|
|
$items = [];
|
|
$request = Mockery::mock(Request::class);
|
|
$request->shouldReceive('uri')->andReturn('/admin/campaigns');
|
|
|
|
$mockTable = Mockery::mock(Table::class);
|
|
$mockTable->shouldReceive('render')->andReturn('');
|
|
|
|
$this->tableFactory->shouldReceive('create')->andReturn($mockTable);
|
|
|
|
$result = $this->service->renderIndex($this->config, $items, $request);
|
|
|
|
expect($result)->toBeInstanceOf(ViewResult::class);
|
|
expect($result->data['actions'])->toBeArray();
|
|
expect($result->data['actions'][0]['label'])->toBe('Create Campaign');
|
|
});
|
|
});
|
|
|
|
describe('renderCreate', function () {
|
|
it('renders create form using AdminPageRenderer', function () {
|
|
$formFields = [
|
|
'name' => ['type' => 'text', 'label' => 'Name'],
|
|
];
|
|
|
|
$mockForm = Mockery::mock();
|
|
$mockForm->shouldReceive('build')->andReturn('<form>...</form>');
|
|
$mockForm->shouldReceive('getId')->andReturn('campaign-form');
|
|
|
|
$this->formFactory->shouldReceive('create')
|
|
->once()
|
|
->andReturn($mockForm);
|
|
|
|
$result = $this->service->renderCreate(
|
|
$this->config,
|
|
$formFields
|
|
);
|
|
|
|
expect($result)->toBeInstanceOf(ViewResult::class);
|
|
expect($result->template)->toBe('admin-form');
|
|
expect($result->data['title'])->toBe('Create Campaign');
|
|
});
|
|
|
|
it('renders create form with help text as subtitle', function () {
|
|
$formFields = [];
|
|
$helpText = 'Fill in the campaign details carefully.';
|
|
|
|
$mockForm = Mockery::mock();
|
|
$mockForm->shouldReceive('build')->andReturn('');
|
|
$mockForm->shouldReceive('getId')->andReturn('form-id');
|
|
|
|
$this->formFactory->shouldReceive('create')->andReturn($mockForm);
|
|
|
|
$result = $this->service->renderCreate(
|
|
$this->config,
|
|
$formFields,
|
|
null,
|
|
$helpText
|
|
);
|
|
|
|
expect($result)->toBeInstanceOf(ViewResult::class);
|
|
expect($result->data['subtitle'])->toBe($helpText);
|
|
});
|
|
});
|
|
|
|
describe('renderEdit', function () {
|
|
it('renders edit form using AdminPageRenderer', function () {
|
|
$formFields = [
|
|
'name' => ['type' => 'text', 'label' => 'Name'],
|
|
];
|
|
|
|
$itemData = [
|
|
'id' => '123',
|
|
'name' => 'Test Campaign',
|
|
'status' => 'active',
|
|
];
|
|
|
|
$metadata = [
|
|
'id' => '123',
|
|
'createdAt' => '2024-01-01 10:00:00',
|
|
'updatedAt' => '2024-01-02 15:30:00',
|
|
];
|
|
|
|
$mockForm = Mockery::mock();
|
|
$mockForm->shouldReceive('build')->andReturn('<form>...</form>');
|
|
$mockForm->shouldReceive('getId')->andReturn('edit-form');
|
|
|
|
$this->formFactory->shouldReceive('create')
|
|
->once()
|
|
->andReturn($mockForm);
|
|
|
|
$result = $this->service->renderEdit(
|
|
$this->config,
|
|
'123',
|
|
$formFields,
|
|
$itemData,
|
|
$metadata
|
|
);
|
|
|
|
expect($result)->toBeInstanceOf(ViewResult::class);
|
|
expect($result->template)->toBe('admin-form');
|
|
expect($result->data['title'])->toBe('Edit Campaign');
|
|
});
|
|
});
|
|
|
|
describe('renderShow', function () {
|
|
it('renders show view using AdminPageRenderer', function () {
|
|
$fields = [
|
|
['label' => 'Name', 'value' => 'Test Campaign', 'type' => 'text'],
|
|
['label' => 'Status', 'value' => 'Active', 'type' => 'badge', 'color' => 'success'],
|
|
];
|
|
|
|
$metadata = [
|
|
'id' => '123',
|
|
'createdAt' => '2024-01-01',
|
|
];
|
|
|
|
$result = $this->service->renderShow(
|
|
$this->config,
|
|
'123',
|
|
$fields,
|
|
$metadata
|
|
);
|
|
|
|
expect($result)->toBeInstanceOf(ViewResult::class);
|
|
expect($result->template)->toBe('admin-show');
|
|
expect($result->data['fields'])->toBe($fields);
|
|
expect($result->data['metadata'])->toBe($metadata);
|
|
expect($result->data['editUrl'])->toBe('/admin/campaigns/edit/123');
|
|
});
|
|
});
|
|
|
|
describe('redirectAfterCreate', function () {
|
|
it('redirects to index after successful create', function () {
|
|
$request = Mockery::mock(Request::class);
|
|
$request->parsedBody = Mockery::mock();
|
|
$request->parsedBody->shouldReceive('get')
|
|
->with('action')
|
|
->andReturn(null);
|
|
|
|
$result = $this->service->redirectAfterCreate(
|
|
$this->config,
|
|
$request,
|
|
'123'
|
|
);
|
|
|
|
expect($result)->toBeInstanceOf(Redirect::class);
|
|
expect($result->url)->toBe('/admin/campaigns');
|
|
});
|
|
|
|
it('redirects to create form when save-and-continue is requested', function () {
|
|
$request = Mockery::mock(Request::class);
|
|
$request->parsedBody = Mockery::mock();
|
|
$request->parsedBody->shouldReceive('get')
|
|
->with('action')
|
|
->andReturn('save-and-continue');
|
|
|
|
$result = $this->service->redirectAfterCreate(
|
|
$this->config,
|
|
$request,
|
|
'123'
|
|
);
|
|
|
|
expect($result->url)->toBe('/admin/campaigns/create');
|
|
});
|
|
});
|
|
|
|
describe('redirectAfterUpdate', function () {
|
|
it('redirects to index after successful update', function () {
|
|
$request = Mockery::mock(Request::class);
|
|
$request->parsedBody = Mockery::mock();
|
|
$request->parsedBody->shouldReceive('get')
|
|
->with('action')
|
|
->andReturn(null);
|
|
|
|
$result = $this->service->redirectAfterUpdate(
|
|
$this->config,
|
|
$request,
|
|
'123'
|
|
);
|
|
|
|
expect($result->url)->toBe('/admin/campaigns');
|
|
});
|
|
|
|
it('redirects to show view when save-and-view is requested', function () {
|
|
$request = Mockery::mock(Request::class);
|
|
$request->parsedBody = Mockery::mock();
|
|
$request->parsedBody->shouldReceive('get')
|
|
->with('action')
|
|
->andReturn('save-and-view');
|
|
|
|
$result = $this->service->redirectAfterUpdate(
|
|
$this->config,
|
|
$request,
|
|
'123'
|
|
);
|
|
|
|
expect($result->url)->toBe('/admin/campaigns/view/123');
|
|
});
|
|
});
|
|
|
|
describe('redirectAfterDelete', function () {
|
|
it('redirects to index after successful delete', function () {
|
|
$result = $this->service->redirectAfterDelete($this->config);
|
|
|
|
expect($result)->toBeInstanceOf(Redirect::class);
|
|
expect($result->url)->toBe('/admin/campaigns');
|
|
});
|
|
});
|
|
|
|
describe('redirectWithError', function () {
|
|
it('redirects back with error message', function () {
|
|
$result = $this->service->redirectWithError('Something went wrong');
|
|
|
|
expect($result)->toBeInstanceOf(Redirect::class);
|
|
});
|
|
|
|
it('redirects with error message and input data', function () {
|
|
$inputData = ['name' => 'Test', 'email' => 'test@example.com'];
|
|
|
|
$result = $this->service->redirectWithError(
|
|
'Validation failed',
|
|
$inputData
|
|
);
|
|
|
|
expect($result)->toBeInstanceOf(Redirect::class);
|
|
});
|
|
});
|
|
});
|