Skip to content

Commit 61e121c

Browse files
committed
Implement mj-table element
Add support for mj-table with all standard attributes including styling, borders, and table layout properties.
1 parent b00cd8e commit 61e121c

File tree

2 files changed

+389
-0
lines changed

2 files changed

+389
-0
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<?php
2+
3+
/**
4+
* PHP MJML Renderer library
5+
*
6+
* @package MadeByDenis\PhpMjmlRenderer
7+
* @link https://github.com/dingo-d/php-mjml-renderer
8+
* @license https://opensource.org/licenses/MIT MIT
9+
*/
10+
11+
declare(strict_types=1);
12+
13+
namespace MadeByDenis\PhpMjmlRenderer\Elements\BodyComponents;
14+
15+
use MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement;
16+
17+
/**
18+
* Mjml Table Element
19+
*
20+
* @link https://documentation.mjml.io/#mj-table
21+
*
22+
* @since 1.0.0
23+
*/
24+
class MjTable extends AbstractElement
25+
{
26+
public const string TAG_NAME = 'mj-table';
27+
28+
public const bool ENDING_TAG = true;
29+
30+
/**
31+
* List of allowed attributes on the element
32+
*
33+
* @var array<string, array<string, string>>
34+
*/
35+
protected array $allowedAttributes = [
36+
'align' => [
37+
'unit' => 'string',
38+
'type' => 'alignment',
39+
'description' => 'table alignment',
40+
'default_value' => 'left',
41+
],
42+
'border' => [
43+
'unit' => 'string',
44+
'type' => 'string',
45+
'description' => 'css border definition',
46+
'default_value' => 'none',
47+
],
48+
'cellpadding' => [
49+
'unit' => 'px',
50+
'type' => 'measure',
51+
'description' => 'space between cells',
52+
'default_value' => '0',
53+
],
54+
'cellspacing' => [
55+
'unit' => 'px',
56+
'type' => 'measure',
57+
'description' => 'space between cell and border',
58+
'default_value' => '0',
59+
],
60+
'color' => [
61+
'unit' => 'color',
62+
'type' => 'color',
63+
'description' => 'text color',
64+
'default_value' => '#000000',
65+
],
66+
'container-background-color' => [
67+
'unit' => 'color',
68+
'type' => 'color',
69+
'description' => 'inner element background color',
70+
'default_value' => '',
71+
],
72+
'css-class' => [
73+
'unit' => 'string',
74+
'type' => 'string',
75+
'description' => 'class name added to root HTML element',
76+
'default_value' => '',
77+
],
78+
'font-family' => [
79+
'unit' => 'string',
80+
'type' => 'string',
81+
'description' => 'font',
82+
'default_value' => 'Ubuntu, Helvetica, Arial, sans-serif',
83+
],
84+
'font-size' => [
85+
'unit' => 'px',
86+
'type' => 'measure',
87+
'description' => 'text size',
88+
'default_value' => '13px',
89+
],
90+
'line-height' => [
91+
'unit' => 'px',
92+
'type' => 'measure',
93+
'description' => 'space between lines',
94+
'default_value' => '22px',
95+
],
96+
'padding' => [
97+
'unit' => 'px',
98+
'type' => 'measure',
99+
'description' => 'supports up to 4 parameters',
100+
'default_value' => '10px 25px',
101+
],
102+
'padding-top' => [
103+
'unit' => 'px',
104+
'type' => 'measure',
105+
'description' => 'top offset',
106+
'default_value' => '',
107+
],
108+
'padding-bottom' => [
109+
'unit' => 'px',
110+
'type' => 'measure',
111+
'description' => 'bottom offset',
112+
'default_value' => '',
113+
],
114+
'padding-left' => [
115+
'unit' => 'px',
116+
'type' => 'measure',
117+
'description' => 'left offset',
118+
'default_value' => '',
119+
],
120+
'padding-right' => [
121+
'unit' => 'px',
122+
'type' => 'measure',
123+
'description' => 'right offset',
124+
'default_value' => '',
125+
],
126+
'role' => [
127+
'unit' => 'string',
128+
'type' => 'string',
129+
'description' => 'ARIA role attribute',
130+
'default_value' => 'presentation',
131+
],
132+
'table-layout' => [
133+
'unit' => 'string',
134+
'type' => 'string',
135+
'description' => 'auto/fixed/initial/inherit',
136+
'default_value' => 'auto',
137+
],
138+
'width' => [
139+
'unit' => 'px,%',
140+
'type' => 'measure',
141+
'description' => 'table width',
142+
'default_value' => '100%',
143+
],
144+
];
145+
146+
protected array $defaultAttributes = [
147+
'align' => 'left',
148+
'border' => 'none',
149+
'cellpadding' => '0',
150+
'cellspacing' => '0',
151+
'color' => '#000000',
152+
'font-family' => 'Ubuntu, Helvetica, Arial, sans-serif',
153+
'font-size' => '13px',
154+
'line-height' => '22px',
155+
'padding' => '10px 25px',
156+
'role' => 'presentation',
157+
'table-layout' => 'auto',
158+
'width' => '100%',
159+
];
160+
161+
public function render(): string
162+
{
163+
$tableAttributes = $this->getHtmlAttributes([
164+
'style' => 'table',
165+
]);
166+
167+
$content = $this->getContent();
168+
169+
return "<table $tableAttributes>$content</table>";
170+
}
171+
172+
/**
173+
* @return array<string, array<string, string>>
174+
*/
175+
public function getStyles(): array
176+
{
177+
return [
178+
'table' => [
179+
'color' => $this->getAttribute('color'),
180+
'font-family' => $this->getAttribute('font-family'),
181+
'font-size' => $this->getAttribute('font-size'),
182+
'line-height' => $this->getAttribute('line-height'),
183+
'table-layout' => $this->getAttribute('table-layout'),
184+
'width' => $this->getAttribute('width'),
185+
'border' => $this->getAttribute('border'),
186+
],
187+
];
188+
}
189+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
<?php
2+
3+
namespace MadeByDenis\PhpMjmlRenderer\Tests\Unit\Elements\BodyComponents;
4+
5+
use MadeByDenis\PhpMjmlRenderer\Elements\BodyComponents\MjTable;
6+
use MadeByDenis\PhpMjmlRenderer\Elements\ElementFactory;
7+
use MadeByDenis\PhpMjmlRenderer\Parser\MjmlNode;
8+
use OutOfBoundsException;
9+
10+
beforeEach(function () {
11+
$this->element = new MjTable();
12+
});
13+
14+
it('is ending tag', function () {
15+
expect($this->element->isEndingTag())->toBe(true);
16+
});
17+
18+
it('returns the correct component name', function () {
19+
expect($this->element->getTagName())->toBe('mj-table');
20+
});
21+
22+
it('returns the correct default attributes', function () {
23+
$attributes = [
24+
'align' => 'left',
25+
'border' => 'none',
26+
'cellpadding' => '0',
27+
'cellspacing' => '0',
28+
'color' => '#000000',
29+
'font-family' => 'Ubuntu, Helvetica, Arial, sans-serif',
30+
'font-size' => '13px',
31+
'line-height' => '22px',
32+
'padding' => '10px 25px',
33+
'role' => 'presentation',
34+
'table-layout' => 'auto',
35+
'width' => '100%',
36+
];
37+
38+
foreach ($attributes as $key => $value) {
39+
expect($this->element->getAttribute($key))->toBe($value);
40+
}
41+
});
42+
43+
it('will throw out of bounds exception if the allowed attribute is not existing', function () {
44+
$this->element->getAllowedAttributeData('invalid-attribute');
45+
})->throws(OutOfBoundsException::class);
46+
47+
it('will return allowed attribute data', function () {
48+
$data = $this->element->getAllowedAttributeData('color');
49+
expect($data)->toBeArray();
50+
expect($data)->toHaveKey('type');
51+
expect($data)->toHaveKey('unit');
52+
});
53+
54+
it('will correctly render a simple table', function () {
55+
$tableNode = new MjmlNode(
56+
'mj-table',
57+
null,
58+
'<tr><td>Cell 1</td><td>Cell 2</td></tr>',
59+
true,
60+
null
61+
);
62+
63+
$factory = new ElementFactory();
64+
$mjTableElement = $factory->create($tableNode);
65+
66+
expect($mjTableElement)->toBeInstanceOf(MjTable::class);
67+
68+
$out = $mjTableElement->render();
69+
70+
expect($out)->toContain('<table');
71+
expect($out)->toContain('<tr><td>Cell 1</td><td>Cell 2</td></tr>');
72+
expect($out)->not->toBeEmpty();
73+
});
74+
75+
it('will correctly render a table with custom color', function () {
76+
$tableNode = new MjmlNode(
77+
'mj-table',
78+
['color' => '#ff0000'],
79+
'<tr><td>Red Text</td></tr>',
80+
true,
81+
null
82+
);
83+
84+
$factory = new ElementFactory();
85+
$mjTableElement = $factory->create($tableNode);
86+
87+
$out = $mjTableElement->render();
88+
89+
expect($out)->toContain('#ff0000');
90+
expect($out)->not->toBeEmpty();
91+
});
92+
93+
it('will correctly render a table with custom border', function () {
94+
$tableNode = new MjmlNode(
95+
'mj-table',
96+
['border' => '1px solid #000'],
97+
'<tr><td>Content</td></tr>',
98+
true,
99+
null
100+
);
101+
102+
$factory = new ElementFactory();
103+
$mjTableElement = $factory->create($tableNode);
104+
105+
$out = $mjTableElement->render();
106+
107+
expect($out)->toContain('1px solid #000');
108+
expect($out)->not->toBeEmpty();
109+
});
110+
111+
it('will correctly render a table with custom width', function () {
112+
$tableNode = new MjmlNode(
113+
'mj-table',
114+
['width' => '600px'],
115+
'<tr><td>Content</td></tr>',
116+
true,
117+
null
118+
);
119+
120+
$factory = new ElementFactory();
121+
$mjTableElement = $factory->create($tableNode);
122+
123+
$out = $mjTableElement->render();
124+
125+
expect($out)->toContain('600px');
126+
expect($out)->not->toBeEmpty();
127+
});
128+
129+
it('will correctly render a table with custom table-layout', function () {
130+
$tableNode = new MjmlNode(
131+
'mj-table',
132+
['table-layout' => 'fixed'],
133+
'<tr><td>Content</td></tr>',
134+
true,
135+
null
136+
);
137+
138+
$factory = new ElementFactory();
139+
$mjTableElement = $factory->create($tableNode);
140+
141+
$out = $mjTableElement->render();
142+
143+
expect($out)->toContain('fixed');
144+
expect($out)->not->toBeEmpty();
145+
});
146+
147+
it('will correctly render a table with all custom properties', function () {
148+
$tableNode = new MjmlNode(
149+
'mj-table',
150+
[
151+
'color' => '#333333',
152+
'font-family' => 'Arial, sans-serif',
153+
'font-size' => '14px',
154+
'line-height' => '20px',
155+
'width' => '500px',
156+
'border' => '2px solid #ccc',
157+
'table-layout' => 'fixed',
158+
],
159+
'<tr><td>Custom Table</td></tr>',
160+
true,
161+
null
162+
);
163+
164+
$factory = new ElementFactory();
165+
$mjTableElement = $factory->create($tableNode);
166+
167+
$out = $mjTableElement->render();
168+
169+
expect($out)->toContain('#333333');
170+
expect($out)->toContain('Arial, sans-serif');
171+
expect($out)->toContain('14px');
172+
expect($out)->toContain('20px');
173+
expect($out)->toContain('500px');
174+
expect($out)->toContain('2px solid #ccc');
175+
expect($out)->toContain('fixed');
176+
expect($out)->toContain('<tr><td>Custom Table</td></tr>');
177+
expect($out)->not->toBeEmpty();
178+
});
179+
180+
it('will correctly render a complex table with multiple rows', function () {
181+
$tableNode = new MjmlNode(
182+
'mj-table',
183+
['border' => '1px solid #ddd'],
184+
'<thead><tr><th>Header 1</th><th>Header 2</th></tr></thead><tbody><tr><td>Row 1, Cell 1</td><td>Row 1, Cell 2</td></tr><tr><td>Row 2, Cell 1</td><td>Row 2, Cell 2</td></tr></tbody>',
185+
true,
186+
null
187+
);
188+
189+
$factory = new ElementFactory();
190+
$mjTableElement = $factory->create($tableNode);
191+
192+
$out = $mjTableElement->render();
193+
194+
expect($out)->toContain('<thead>');
195+
expect($out)->toContain('<th>Header 1</th>');
196+
expect($out)->toContain('<tbody>');
197+
expect($out)->toContain('Row 1, Cell 1');
198+
expect($out)->toContain('Row 2, Cell 2');
199+
expect($out)->not->toBeEmpty();
200+
});

0 commit comments

Comments
 (0)