- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
279 lines
9.4 KiB
PHP
279 lines
9.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Admin\Factories\AdminFormFactory;
|
|
use App\Framework\Admin\Services\CrudService;
|
|
use App\Framework\Admin\ValueObjects\CrudConfig;
|
|
use App\Framework\Http\HttpRequest;
|
|
use App\Framework\Http\Method;
|
|
use App\Framework\Http\Responses\Redirect;
|
|
use App\Framework\Http\Responses\ViewResult;
|
|
use App\Framework\View\TemplateRenderer;
|
|
|
|
beforeEach(function () {
|
|
$this->renderer = Mockery::mock(TemplateRenderer::class);
|
|
$this->formFactory = Mockery::mock(AdminFormFactory::class);
|
|
$this->service = new CrudService($this->renderer, $this->formFactory);
|
|
|
|
$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 and pagination', function () {
|
|
$items = [
|
|
['id' => '1', 'name' => 'Campaign 1', 'status' => 'active'],
|
|
['id' => '2', 'name' => 'Campaign 2', 'status' => 'draft'],
|
|
];
|
|
|
|
$pagination = [
|
|
'current_page' => 1,
|
|
'total_pages' => 5,
|
|
'per_page' => 10,
|
|
];
|
|
|
|
$request = Mockery::mock(HttpRequest::class);
|
|
$request->shouldReceive('uri')->andReturn('/admin/campaigns');
|
|
|
|
$result = $this->service->renderIndex(
|
|
$this->config,
|
|
$items,
|
|
$request,
|
|
$pagination
|
|
);
|
|
|
|
expect($result)->toBeInstanceOf(ViewResult::class);
|
|
expect($result->template)->toBe('crud-index');
|
|
expect($result->data['items'])->toBe($items);
|
|
expect($result->data['pagination'])->toBe($pagination);
|
|
expect($result->data['resource'])->toBe('campaigns');
|
|
expect($result->data['createUrl'])->toBe('/admin/campaigns/create');
|
|
});
|
|
});
|
|
|
|
describe('renderCreate', function () {
|
|
it('renders create form with default configuration', function () {
|
|
$formFields = [
|
|
['type' => 'text', 'name' => 'name', '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('crud-create');
|
|
expect($result->data['title'])->toBe('Create Campaign');
|
|
expect($result->data['formId'])->toBe('campaign-form');
|
|
expect($result->data['backUrl'])->toBe('/admin/campaigns');
|
|
});
|
|
|
|
it('renders create form with help text', 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->data['helpText'])->toBe($helpText);
|
|
});
|
|
});
|
|
|
|
describe('renderEdit', function () {
|
|
it('renders edit form with item data and metadata', function () {
|
|
$formFields = [
|
|
['type' => 'text', 'name' => 'name', '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('crud-edit');
|
|
expect($result->data['title'])->toBe('Edit Campaign');
|
|
expect($result->data['metadata'])->toBe($metadata);
|
|
expect($result->data['deleteUrl'])->toBe('/admin/campaigns/delete/123');
|
|
});
|
|
});
|
|
|
|
describe('renderShow', function () {
|
|
it('renders show view with fields and metadata', 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('crud-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(HttpRequest::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(HttpRequest::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(HttpRequest::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(HttpRequest::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);
|
|
});
|
|
});
|
|
});
|