fix: DockerSecretsResolver - don't normalize absolute paths like /var/www/html/...
Some checks failed
Deploy Application / deploy (push) Has been cancelled

This commit is contained in:
2025-11-24 21:28:25 +01:00
parent 4eb7134853
commit 77abc65cd7
1327 changed files with 91915 additions and 9909 deletions

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
use App\Application\Admin\Cms\Services\ContentPreviewService;
use App\Domain\Cms\Services\ContentRenderer;
use App\Domain\Cms\ValueObjects\BlockData;
use App\Domain\Cms\ValueObjects\BlockId;
use App\Domain\Cms\ValueObjects\BlockType;
use App\Domain\Cms\ValueObjects\ContentBlock;
use App\Domain\Cms\ValueObjects\ContentBlocks;
use App\Framework\DateTime\Clock;
describe('ContentPreviewService', function () {
beforeEach(function () {
$this->clock = new Clock();
// Create a mock ContentRenderer
$this->contentRenderer = Mockery::mock(ContentRenderer::class);
$this->service = new ContentPreviewService(
$this->contentRenderer,
$this->clock
);
});
it('renders preview HTML from ContentBlocks', function () {
$blocks = ContentBlocks::fromArray([
[
'id' => 'block-1',
'type' => 'text',
'data' => ['content' => '<p>Test content</p>'],
],
]);
$this->contentRenderer
->shouldReceive('render')
->once()
->andReturn('<div class="cms-text"><p>Test content</p></div>');
$html = $this->service->renderPreview($blocks);
expect($html)->toBeString();
expect($html)->not->toBeEmpty();
expect($html)->toContain('Test content');
});
it('renders multiple blocks in preview', function () {
$blocks = ContentBlocks::fromArray([
[
'id' => 'block-1',
'type' => 'hero',
'data' => ['title' => 'Hero Title'],
],
[
'id' => 'block-2',
'type' => 'text',
'data' => ['content' => '<p>Text content</p>'],
],
]);
$this->contentRenderer
->shouldReceive('render')
->once()
->andReturn('<div class="cms-hero">Hero Title</div><div class="cms-text"><p>Text content</p></div>');
$html = $this->service->renderPreview($blocks);
expect($html)->toBeString();
expect($html)->toContain('Hero Title');
expect($html)->toContain('Text content');
});
it('renders empty blocks as empty string', function () {
$blocks = ContentBlocks::fromArray([]);
$this->contentRenderer
->shouldReceive('render')
->once()
->andReturn('');
$html = $this->service->renderPreview($blocks);
expect($html)->toBe('');
});
it('does not use caching for preview', function () {
$blocks = ContentBlocks::fromArray([
[
'id' => 'block-1',
'type' => 'text',
'data' => ['content' => '<p>Test</p>'],
],
]);
$this->contentRenderer
->shouldReceive('render')
->once()
->andReturn('<div>Test</div>');
// Call twice - should render both times (no caching)
$html1 = $this->service->renderPreview($blocks);
$html2 = $this->service->renderPreview($blocks);
expect($html1)->toBe($html2);
// Verify render was called twice (no caching)
$this->contentRenderer->shouldHaveReceived('render')->twice();
});
});

View File

@@ -199,3 +199,8 @@ describe('AdminLayoutProcessor', function () {
});
});

View File

@@ -0,0 +1,213 @@
<?php
declare(strict_types=1);
use App\Application\Admin\Cms\Services\ContentPreviewService;
use App\Application\LiveComponents\Admin\BlockEditor\BlockEditorComponent;
use App\Application\LiveComponents\Admin\BlockEditor\BlockEditorState;
use App\Domain\Cms\Services\BlockTemplateService;
use App\Domain\Cms\Services\BlockTypeRegistry;
use App\Framework\LiveComponents\ComponentRegistry;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
describe('BlockEditorComponent', function () {
beforeEach(function () {
$this->componentId = ComponentId::generate('admin-block-editor');
$this->blockTypeRegistry = new BlockTypeRegistry();
$this->componentRegistry = Mockery::mock(ComponentRegistry::class);
// Mock ContentPreviewService
$this->previewService = Mockery::mock(ContentPreviewService::class);
$this->previewService
->shouldReceive('renderPreview')
->andReturn('<div>Preview HTML</div>');
// Mock BlockTemplateService
$this->templateService = Mockery::mock(BlockTemplateService::class);
$this->templateService
->shouldReceive('getAllTemplates')
->andReturn([
'landing-page' => [
'name' => 'Landing Page',
'description' => 'Test template',
'blocks' => [],
],
]);
$this->state = BlockEditorState::empty();
});
it('creates component with initial state', function () {
$component = new BlockEditorComponent(
id: $this->componentId,
state: $this->state,
blockTypeRegistry: $this->blockTypeRegistry,
previewService: $this->previewService,
componentRegistry: $this->componentRegistry,
templateService: $this->templateService
);
expect($component->id)->toBe($this->componentId);
expect($component->state)->toBe($this->state);
});
it('adds a new block', function () {
$component = new BlockEditorComponent(
id: $this->componentId,
state: $this->state,
blockTypeRegistry: $this->blockTypeRegistry,
previewService: $this->previewService,
componentRegistry: $this->componentRegistry,
templateService: $this->templateService
);
$newState = $component->addBlock('hero');
expect($newState->blocks)->toHaveCount(1);
expect($newState->blocks[0]['type'])->toBe('hero');
});
it('does not add block for invalid block type', function () {
$component = new BlockEditorComponent(
id: $this->componentId,
state: $this->state,
blockTypeRegistry: $this->blockTypeRegistry,
previewService: $this->previewService,
componentRegistry: $this->componentRegistry,
templateService: $this->templateService
);
$newState = $component->addBlock('invalid-type');
expect($newState->blocks)->toBeEmpty();
});
it('removes a block', function () {
$state = $this->state->withBlockAdded('hero', ['title' => 'Test']);
$component = new BlockEditorComponent(
id: $this->componentId,
state: $state,
blockTypeRegistry: $this->blockTypeRegistry,
previewService: $this->previewService,
componentRegistry: $this->componentRegistry,
templateService: $this->templateService
);
$blockId = $state->blocks[0]['id'];
$newState = $component->removeBlock($blockId);
expect($newState->blocks)->toBeEmpty();
});
it('updates block field', function () {
$state = $this->state->withBlockAdded('text', ['content' => 'Old content']);
$component = new BlockEditorComponent(
id: $this->componentId,
state: $state,
blockTypeRegistry: $this->blockTypeRegistry,
previewService: $this->previewService,
componentRegistry: $this->componentRegistry,
templateService: $this->templateService
);
$blockId = $state->blocks[0]['id'];
$newState = $component->updateBlockField($blockId, 'content', 'New content');
expect($newState->blocks[0]['data']['content'])->toBe('New content');
});
it('reorders blocks', function () {
$state = $this->state
->withBlockAdded('hero', ['title' => 'Hero'])
->withBlockAdded('text', ['content' => 'Text']);
$component = new BlockEditorComponent(
id: $this->componentId,
state: $state,
blockTypeRegistry: $this->blockTypeRegistry,
previewService: $this->previewService,
componentRegistry: $this->componentRegistry,
templateService: $this->templateService
);
$blockIds = array_map(fn ($block) => $block['id'], $state->blocks);
$reorderedIds = array_reverse($blockIds);
$newState = $component->reorderBlocks($reorderedIds);
expect($newState->blocks[0]['type'])->toBe('text');
expect($newState->blocks[1]['type'])->toBe('hero');
});
it('toggles preview mode', function () {
$component = new BlockEditorComponent(
id: $this->componentId,
state: $this->state,
blockTypeRegistry: $this->blockTypeRegistry,
previewService: $this->previewService,
componentRegistry: $this->componentRegistry,
templateService: $this->templateService
);
$newState = $component->togglePreview();
expect($newState->isPreviewMode)->toBeTrue();
expect($newState->previewHtml)->not->toBeNull();
});
it('applies template', function () {
$this->templateService
->shouldReceive('hasTemplate')
->with('landing-page')
->andReturn(true);
$this->templateService
->shouldReceive('getTemplate')
->with('landing-page')
->andReturn([
'name' => 'Landing Page',
'description' => 'Test',
'blocks' => [
[
'type' => 'hero',
'data' => ['title' => 'Hero Title'],
],
],
]);
$component = new BlockEditorComponent(
id: $this->componentId,
state: $this->state,
blockTypeRegistry: $this->blockTypeRegistry,
previewService: $this->previewService,
componentRegistry: $this->componentRegistry,
templateService: $this->templateService
);
$newState = $component->applyTemplate('landing-page');
expect($newState->blocks)->not->toBeEmpty();
expect($newState->blocks[0]['type'])->toBe('hero');
});
it('does not apply invalid template', function () {
$this->templateService
->shouldReceive('hasTemplate')
->with('invalid')
->andReturn(false);
$component = new BlockEditorComponent(
id: $this->componentId,
state: $this->state,
blockTypeRegistry: $this->blockTypeRegistry,
previewService: $this->previewService,
componentRegistry: $this->componentRegistry,
templateService: $this->templateService
);
$newState = $component->applyTemplate('invalid');
expect($newState->blocks)->toBeEmpty();
});
});

View File

@@ -0,0 +1,159 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Application\LiveComponents\Drawer;
use App\Application\LiveComponents\Drawer\DrawerComponent;
use App\Application\LiveComponents\Drawer\DrawerState;
use App\Framework\LiveComponents\ValueObjects\ComponentData;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use PHPUnit\Framework\TestCase;
final class DrawerComponentTest extends TestCase
{
public function testDrawerComponentCanBeCreated(): void
{
$component = new DrawerComponent(
id: ComponentId::fromString('drawer:test'),
isOpen: false,
position: 'left',
width: '400px'
);
$this->assertInstanceOf(DrawerComponent::class, $component);
$this->assertFalse($component->state->isOpen);
$this->assertEquals('left', $component->state->position);
}
public function testOpenActionOpensDrawer(): void
{
$component = new DrawerComponent(
id: ComponentId::fromString('drawer:test')
);
$newState = $component->open('Test Title', 'Test Content', 'right', '500px');
$this->assertTrue($newState->isOpen);
$this->assertEquals('Test Title', $newState->title);
$this->assertEquals('Test Content', $newState->content);
$this->assertEquals('right', $newState->position);
$this->assertEquals('500px', $newState->width);
}
public function testCloseActionClosesDrawer(): void
{
$component = new DrawerComponent(
id: ComponentId::fromString('drawer:test'),
isOpen: true
);
$newState = $component->close();
$this->assertFalse($newState->isOpen);
}
public function testToggleActionTogglesDrawer(): void
{
$component = new DrawerComponent(
id: ComponentId::fromString('drawer:test'),
isOpen: false
);
$openedState = $component->toggle();
$this->assertTrue($openedState->isOpen);
// Simulate state update
$component = new DrawerComponent(
id: ComponentId::fromString('drawer:test'),
initialData: ComponentData::fromArray($openedState->toArray())
);
$closedState = $component->toggle();
$this->assertFalse($closedState->isOpen);
}
public function testUpdateContentActionUpdatesContent(): void
{
$component = new DrawerComponent(
id: ComponentId::fromString('drawer:test'),
isOpen: true,
content: 'Old Content'
);
$newState = $component->updateContent('New Content');
$this->assertEquals('New Content', $newState->content);
$this->assertTrue($newState->isOpen); // State should remain open
}
public function testChangePositionActionChangesPosition(): void
{
$component = new DrawerComponent(
id: ComponentId::fromString('drawer:test'),
position: 'left'
);
$newState = $component->changePosition('right');
$this->assertEquals('right', $newState->position);
}
public function testDrawerStateFromArray(): void
{
$data = [
'is_open' => true,
'position' => 'right',
'width' => '500px',
'show_overlay' => true,
'title' => 'Test',
'content' => 'Content',
'close_on_overlay' => true,
'close_on_escape' => true,
'animation' => 'slide',
'z_index' => 1050,
];
$state = DrawerState::fromArray($data);
$this->assertTrue($state->isOpen);
$this->assertEquals('right', $state->position);
$this->assertEquals('500px', $state->width);
}
public function testDrawerStateToArray(): void
{
$state = new DrawerState(
isOpen: true,
position: 'left',
width: '400px'
);
$array = $state->toArray();
$this->assertTrue($array['is_open']);
$this->assertEquals('left', $array['position']);
$this->assertEquals('400px', $array['width']);
}
public function testDrawerStatePositionHelpers(): void
{
$leftState = new DrawerState(position: 'left');
$rightState = new DrawerState(position: 'right');
$this->assertTrue($leftState->isLeft());
$this->assertFalse($leftState->isRight());
$this->assertTrue($rightState->isRight());
$this->assertFalse($rightState->isLeft());
}
public function testDrawerStateGetPositionClass(): void
{
$state = new DrawerState(position: 'left');
$this->assertEquals('drawer--left', $state->getPositionClass());
$state = new DrawerState(position: 'right');
$this->assertEquals('drawer--right', $state->getPositionClass());
}
}

View File

@@ -0,0 +1,155 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Application\LiveComponents\Popover;
use App\Application\LiveComponents\Popover\PopoverComponent;
use App\Application\LiveComponents\Popover\PopoverState;
use App\Framework\LiveComponents\ValueObjects\ComponentData;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use PHPUnit\Framework\TestCase;
final class PopoverComponentTest extends TestCase
{
public function testPopoverComponentCanBeCreated(): void
{
$component = new PopoverComponent(
id: ComponentId::fromString('popover:test'),
isVisible: false,
position: 'top',
anchorId: 'test-anchor'
);
$this->assertInstanceOf(PopoverComponent::class, $component);
$this->assertFalse($component->state->isVisible);
$this->assertEquals('top', $component->state->position);
$this->assertEquals('test-anchor', $component->state->anchorId);
}
public function testShowActionShowsPopover(): void
{
$component = new PopoverComponent(
id: ComponentId::fromString('popover:test')
);
$newState = $component->show('Test Content', 'Test Title', 'test-anchor', 'bottom');
$this->assertTrue($newState->isVisible);
$this->assertEquals('Test Content', $newState->content);
$this->assertEquals('Test Title', $newState->title);
$this->assertEquals('test-anchor', $newState->anchorId);
$this->assertEquals('bottom', $newState->position);
}
public function testHideActionHidesPopover(): void
{
$component = new PopoverComponent(
id: ComponentId::fromString('popover:test'),
isVisible: true
);
$newState = $component->hide();
$this->assertFalse($newState->isVisible);
}
public function testToggleActionTogglesPopover(): void
{
$component = new PopoverComponent(
id: ComponentId::fromString('popover:test'),
isVisible: false
);
$visibleState = $component->toggle();
$this->assertTrue($visibleState->isVisible);
// Simulate state update
$component = new PopoverComponent(
id: ComponentId::fromString('popover:test'),
initialData: ComponentData::fromArray($visibleState->toArray())
);
$hiddenState = $component->toggle();
$this->assertFalse($hiddenState->isVisible);
}
public function testUpdatePositionActionUpdatesPosition(): void
{
$component = new PopoverComponent(
id: ComponentId::fromString('popover:test'),
position: 'top'
);
$newState = $component->updatePosition('right');
$this->assertEquals('right', $newState->position);
}
public function testUpdateAnchorActionUpdatesAnchor(): void
{
$component = new PopoverComponent(
id: ComponentId::fromString('popover:test'),
anchorId: 'old-anchor'
);
$newState = $component->updateAnchor('new-anchor');
$this->assertEquals('new-anchor', $newState->anchorId);
}
public function testPopoverStateFromArray(): void
{
$data = [
'is_visible' => true,
'position' => 'bottom',
'anchor_id' => 'test-anchor',
'content' => 'Test Content',
'title' => 'Test Title',
'show_arrow' => true,
'offset' => 10,
'close_on_outside_click' => true,
'z_index' => 1060,
];
$state = PopoverState::fromArray($data);
$this->assertTrue($state->isVisible);
$this->assertEquals('bottom', $state->position);
$this->assertEquals('test-anchor', $state->anchorId);
}
public function testPopoverStateToArray(): void
{
$state = new PopoverState(
isVisible: true,
position: 'top',
anchorId: 'test-anchor'
);
$array = $state->toArray();
$this->assertTrue($array['is_visible']);
$this->assertEquals('top', $array['position']);
$this->assertEquals('test-anchor', $array['anchor_id']);
}
public function testPopoverStateIsAutoPosition(): void
{
$autoState = new PopoverState(position: 'auto');
$topState = new PopoverState(position: 'top');
$this->assertTrue($autoState->isAutoPosition());
$this->assertFalse($topState->isAutoPosition());
}
public function testPopoverStateGetPositionClass(): void
{
$state = new PopoverState(position: 'top');
$this->assertEquals('popover--top', $state->getPositionClass());
$state = new PopoverState(position: 'bottom');
$this->assertEquals('popover--bottom', $state->getPositionClass());
}
}