diff --git a/src/Elements/BodyComponents/MjWrapper.php b/src/Elements/BodyComponents/MjWrapper.php new file mode 100644 index 0000000..b169583 --- /dev/null +++ b/src/Elements/BodyComponents/MjWrapper.php @@ -0,0 +1,261 @@ +> + */ + protected array $allowedAttributes = [ + 'background-color' => [ + 'unit' => 'color', + 'type' => 'color', + 'description' => 'wrapper background color', + 'default_value' => '', + ], + 'background-url' => [ + 'unit' => 'string', + 'type' => 'string', + 'description' => 'wrapper background image url', + 'default_value' => '', + ], + 'background-repeat' => [ + 'unit' => 'string', + 'type' => 'string', + 'description' => 'wrapper background repeat value', + 'default_value' => 'repeat', + ], + 'background-size' => [ + 'unit' => 'string', + 'type' => 'string', + 'description' => 'wrapper background size', + 'default_value' => 'auto', + ], + 'background-position' => [ + 'unit' => 'string', + 'type' => 'string', + 'description' => 'wrapper background position', + 'default_value' => 'top center', + ], + 'background-position-x' => [ + 'unit' => 'string', + 'type' => 'string', + 'description' => 'wrapper background position x value', + 'default_value' => '', + ], + 'background-position-y' => [ + 'unit' => 'string', + 'type' => 'string', + 'description' => 'wrapper background position y value', + 'default_value' => '', + ], + 'border' => [ + 'unit' => 'string', + 'type' => 'string', + 'description' => 'wrapper border format', + 'default_value' => 'none', + ], + 'border-bottom' => [ + 'unit' => 'string', + 'type' => 'string', + 'description' => 'wrapper border bottom', + 'default_value' => '', + ], + 'border-left' => [ + 'unit' => 'string', + 'type' => 'string', + 'description' => 'wrapper border left', + 'default_value' => '', + ], + 'border-radius' => [ + 'unit' => 'px', + 'type' => 'measure', + 'description' => 'wrapper border radius', + 'default_value' => '', + ], + 'border-right' => [ + 'unit' => 'string', + 'type' => 'string', + 'description' => 'wrapper border right', + 'default_value' => '', + ], + 'border-top' => [ + 'unit' => 'string', + 'type' => 'string', + 'description' => 'wrapper border top', + 'default_value' => '', + ], + 'css-class' => [ + 'unit' => 'string', + 'type' => 'string', + 'description' => 'class name added to root HTML element', + 'default_value' => '', + ], + 'padding' => [ + 'unit' => 'px', + 'type' => 'string', + 'description' => 'supports up to 4 parameters', + 'default_value' => '20px 0', + ], + 'padding-bottom' => [ + 'unit' => 'px', + 'type' => 'measure', + 'description' => 'bottom offset', + 'default_value' => '', + ], + 'padding-left' => [ + 'unit' => 'px', + 'type' => 'measure', + 'description' => 'left offset', + 'default_value' => '', + ], + 'padding-right' => [ + 'unit' => 'px', + 'type' => 'measure', + 'description' => 'right offset', + 'default_value' => '', + ], + 'padding-top' => [ + 'unit' => 'px', + 'type' => 'measure', + 'description' => 'top offset', + 'default_value' => '', + ], + 'text-align' => [ + 'unit' => 'string', + 'type' => 'alignment', + 'description' => 'left/right/center/justify', + 'default_value' => 'center', + ], + ]; + + protected array $defaultAttributes = [ + 'background-repeat' => 'repeat', + 'background-size' => 'auto', + 'background-position' => 'top center', + 'border' => 'none', + 'padding' => '20px 0', + 'text-align' => 'center', + ]; + + public function render(): string + { + $tableAttributes = $this->getHtmlAttributes([ + 'align' => 'center', + 'class' => $this->getAttribute('css-class'), + 'background' => $this->getAttribute('background-url'), + 'border' => '0', + 'cellpadding' => '0', + 'cellspacing' => '0', + 'role' => 'presentation', + 'style' => 'table', + ]); + + $tdAttributes = $this->getHtmlAttributes([ + 'style' => 'td', + ]); + + $children = $this->getChildren() ?? []; + $content = $this->renderChildren($children, []); + + return " + + + + + +
+ $content +
"; + } + + /** + * @return array> + */ + public function getStyles(): array + { + $background = $this->getAttribute('background-url') ? + [ + 'background' => $this->getBackground(), + 'background-position' => $this->getAttribute('background-position'), + 'background-repeat' => $this->getAttribute('background-repeat'), + 'background-size' => $this->getAttribute('background-size'), + ] : + [ + 'background' => $this->getAttribute('background-color'), + 'background-color' => $this->getAttribute('background-color'), + ]; + + return [ + 'table' => array_merge($background, [ + 'width' => '100%', + 'border-radius' => $this->getAttribute('border-radius'), + ]), + 'td' => [ + 'border' => $this->getAttribute('border'), + 'border-bottom' => $this->getAttribute('border-bottom'), + 'border-left' => $this->getAttribute('border-left'), + 'border-right' => $this->getAttribute('border-right'), + 'border-top' => $this->getAttribute('border-top'), + 'direction' => 'ltr', + 'font-size' => '0px', + 'padding' => $this->getAttribute('padding'), + 'padding-bottom' => $this->getAttribute('padding-bottom'), + 'padding-left' => $this->getAttribute('padding-left'), + 'padding-right' => $this->getAttribute('padding-right'), + 'padding-top' => $this->getAttribute('padding-top'), + 'text-align' => $this->getAttribute('text-align'), + ], + ]; + } + + private function getBackground(): string + { + $bgUrl = $this->getAttribute('background-url'); + $bgSize = $this->getAttribute('background-size'); + + $backgroundParts = []; + + if ($this->getAttribute('background-color')) { + $backgroundParts[] = $this->getAttribute('background-color'); + } + + if ($bgUrl) { + $backgroundParts[] = "url('$bgUrl')"; + $backgroundParts[] = $this->getAttribute('background-position'); + $backgroundParts[] = "/ $bgSize"; + $backgroundParts[] = $this->getAttribute('background-repeat'); + } + + return implode(' ', array_filter($backgroundParts)); + } +} diff --git a/tests/Unit/Elements/BodyComponents/MjWrapperTest.php b/tests/Unit/Elements/BodyComponents/MjWrapperTest.php new file mode 100644 index 0000000..64ef245 --- /dev/null +++ b/tests/Unit/Elements/BodyComponents/MjWrapperTest.php @@ -0,0 +1,205 @@ +element = new MjWrapper(); +}); + +it('is not ending tag', function () { + expect($this->element->isEndingTag())->toBe(false); +}); + +it('returns the correct component name', function () { + expect($this->element->getTagName())->toBe('mj-wrapper'); +}); + +it('returns the correct default attributes', function () { + $attributes = [ + 'background-repeat' => 'repeat', + 'background-size' => 'auto', + 'background-position' => 'top center', + 'border' => 'none', + 'padding' => '20px 0', + 'text-align' => 'center', + ]; + + foreach ($attributes as $key => $value) { + expect($this->element->getAttribute($key))->toBe($value); + } +}); + +it('will throw out of bounds exception if the allowed attribute is not existing', function () { + $this->element->getAllowedAttributeData('invalid-attribute'); +})->throws(OutOfBoundsException::class); + +it('will return allowed attribute data', function () { + $data = $this->element->getAllowedAttributeData('background-color'); + expect($data)->toBeArray(); + expect($data)->toHaveKey('type'); + expect($data)->toHaveKey('unit'); +}); + +it('will correctly render a simple wrapper', function () { + $wrapperNode = new MjmlNode( + 'mj-wrapper', + null, + null, + false, + null + ); + + $factory = new ElementFactory(); + $mjWrapperElement = $factory->create($wrapperNode); + + expect($mjWrapperElement)->toBeInstanceOf(MjWrapper::class); + + $out = $mjWrapperElement->render(); + + expect($out)->toContain('toContain('role="presentation"'); + expect($out)->not->toBeEmpty(); +}); + +it('will correctly render a wrapper with background color', function () { + $wrapperNode = new MjmlNode( + 'mj-wrapper', + ['background-color' => '#f0f0f0'], + null, + false, + null + ); + + $factory = new ElementFactory(); + $mjWrapperElement = $factory->create($wrapperNode); + + $out = $mjWrapperElement->render(); + + expect($out)->toContain('#f0f0f0'); + expect($out)->not->toBeEmpty(); +}); + +it('will correctly render a wrapper with background URL', function () { + $wrapperNode = new MjmlNode( + 'mj-wrapper', + ['background-url' => 'https://example.com/bg.jpg'], + null, + false, + null + ); + + $factory = new ElementFactory(); + $mjWrapperElement = $factory->create($wrapperNode); + + $out = $mjWrapperElement->render(); + + expect($out)->toContain('https://example.com/bg.jpg'); + expect($out)->not->toBeEmpty(); +}); + +it('will correctly render a wrapper with padding', function () { + $wrapperNode = new MjmlNode( + 'mj-wrapper', + ['padding' => '30px 10px'], + null, + false, + null + ); + + $factory = new ElementFactory(); + $mjWrapperElement = $factory->create($wrapperNode); + + $out = $mjWrapperElement->render(); + + expect($out)->toContain('30px 10px'); + expect($out)->not->toBeEmpty(); +}); + +it('will correctly render a wrapper with border', function () { + $wrapperNode = new MjmlNode( + 'mj-wrapper', + ['border' => '2px solid #000'], + null, + false, + null + ); + + $factory = new ElementFactory(); + $mjWrapperElement = $factory->create($wrapperNode); + + $out = $mjWrapperElement->render(); + + expect($out)->toContain('2px solid #000'); + expect($out)->not->toBeEmpty(); +}); + +it('will correctly render a wrapper with border-radius', function () { + $wrapperNode = new MjmlNode( + 'mj-wrapper', + ['border-radius' => '10px'], + null, + false, + null + ); + + $factory = new ElementFactory(); + $mjWrapperElement = $factory->create($wrapperNode); + + $out = $mjWrapperElement->render(); + + expect($out)->toContain('10px'); + expect($out)->not->toBeEmpty(); +}); + +it('will correctly render a wrapper with text-align', function () { + $wrapperNode = new MjmlNode( + 'mj-wrapper', + ['text-align' => 'left'], + null, + false, + null + ); + + $factory = new ElementFactory(); + $mjWrapperElement = $factory->create($wrapperNode); + + $out = $mjWrapperElement->render(); + + expect($out)->toContain('left'); + expect($out)->not->toBeEmpty(); +}); + +it('will correctly render a wrapper with all custom properties', function () { + $wrapperNode = new MjmlNode( + 'mj-wrapper', + [ + 'background-color' => '#e0e0e0', + 'background-url' => 'https://example.com/image.jpg', + 'padding' => '40px 20px', + 'border' => '1px solid #ccc', + 'border-radius' => '8px', + 'text-align' => 'right', + ], + null, + false, + null + ); + + $factory = new ElementFactory(); + $mjWrapperElement = $factory->create($wrapperNode); + + $out = $mjWrapperElement->render(); + + expect($out)->toContain('#e0e0e0'); + expect($out)->toContain('https://example.com/image.jpg'); + expect($out)->toContain('40px 20px'); + expect($out)->toContain('1px solid #ccc'); + expect($out)->toContain('8px'); + expect($out)->toContain('right'); + expect($out)->not->toBeEmpty(); +});