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('
');
$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('');
$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('');
$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);
});
});
});